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

Fi 1977 slow #64

Merged
merged 6 commits into from
Aug 1, 2023
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 build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies {
implementation("org.slf4j", "slf4j-log4j12", "1.7.30")

// Web Server
implementation("com.sparkjava", "spark-core", "2.9.1")
implementation("com.sparkjava", "spark-core", "2.9.4")

// Testing stuff
testImplementation("org.junit.jupiter", "junit-jupiter", "5.9.3")
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/org/mitre/inferno/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ public class App {

/**
* Starting point for the Validation Service.
* <p>Passing the 'prepare' argument causes the FHIR artifacts needed to be downloaded.</p>
* <p>
* Passing the 'prepare' argument causes the FHIR artifacts needed to be
* downloaded.
* </p>
*
* @param args the application initialization arguments
*/
public static void main(String[] args) {
Expand Down Expand Up @@ -60,11 +64,11 @@ private static void startApp() {
Logger logger = LoggerFactory.getLogger(App.class);
logger.info("Starting Server...");
SparkUtils.createServerWithRequestLog(logger);
Endpoints.setupLoadingRoutes(getPortNumber());
Endpoints.getInstance(
initializeValidator(),
initializePathEvaluator(),
getPortNumber()
);
getPortNumber());
}

private static int getPortNumber() {
Expand Down
122 changes: 105 additions & 17 deletions src/main/java/org/mitre/inferno/rest/Endpoints.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package org.mitre.inferno.rest;

import static spark.Spark.before;
import static spark.Spark.get;
import static spark.Spark.options;
import static spark.Spark.port;
import static spark.Spark.post;
import static spark.Spark.put;
import static spark.Spark.unmap;

import com.google.gson.Gson;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.mitre.inferno.FHIRPathEvaluator;
import org.mitre.inferno.Validator;
import spark.ResponseTransformer;
Expand All @@ -16,44 +27,121 @@ public class Endpoints {
private final Validator validator;
private final FHIRPathEvaluator pathEvaluator;

private Endpoints(Validator validator, FHIRPathEvaluator evaluator, int port) {
private Endpoints(Validator validator, FHIRPathEvaluator evaluator) {
this.validator = validator;
this.pathEvaluator = evaluator;
port(port);
createRoutes();
}

/**
* This adds permissive CORS headers to all requests.
*/
private static void setHeaders() {

before((req, res) -> {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
res.header("Access-Control-Allow-Headers", "*");
});

// This responds to OPTIONS requests, used by browsers to "preflight" check CORS
// requests,
// with a 200 OK response with no content and the CORS headers above
options("*", (req, res) -> "");

}

/**
* Create a wait message while the validator loads.
*
* @throws Exception if operation outcome cannot be parsed
*/
private static String generateWaitMessage() throws Exception {

OperationOutcome.IssueSeverity sev = OperationOutcome.IssueSeverity.ERROR;
OperationOutcomeIssueComponent issue = new OperationOutcomeIssueComponent(
sev,
IssueType.INCOMPLETE);
String message = "Validator still loading... please wait.";
issue.setDiagnostics(message);
issue.setDetails(new CodeableConcept().setText(message));
issue.addExtension(
"http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-line",
new IntegerType(1));
issue.addExtension(
"http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-col",
new IntegerType(1));
issue.addExtension(
"http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-source",
new CodeType("ValidationService"));
OperationOutcome oo = new OperationOutcome(issue);
return new JsonParser().composeString(oo);
}

/**
* Set up temporary routes while the validator still loads.
*
* @param port the port on which to listen for requests
*/
public static void setupLoadingRoutes(int port) {
port(port);
setHeaders();

get("*", (req, res) -> {
res.type("application/fhir+json");
res.status(503);
res.body("Validator still loading... please wait.");
return generateWaitMessage();
});
post("*", (req, res) -> {
res.type("application/fhir+json");
res.status(503);
res.body("Validator still loading... please wait.");
return generateWaitMessage();
});
put("*", (req, res) -> {
res.type("application/fhir+json");
res.status(503);
res.body("Validator still loading... please wait.");
return generateWaitMessage();
});
}

/**
* Remove temporary routes set. This is meant to be called once the validator
* has been loaded.
*/
public static void teardownLoadingRoutes() {
unmap("*", "get");
unmap("*", "post");
unmap("*", "put");
}

/**
* Get the existing Endpoints or create one if it does not already exist.
*
* @param validator the Validator that should be used at the /validator endpoint.
* @param validator the Validator that should be used at the /validator
* endpoint.
* Passing null will skip setting up the /validator endpoint.
* @param evaluator the FHIRPathEvaluator that should be used at the /fhirpath endpoint.
* @param evaluator the FHIRPathEvaluator that should be used at the /fhirpath
* endpoint.
* Passing null will skip setting up the /fhirpath endpoint.
* @param port the port to listen for requests on
* @param port the port to listen for requests on
* @return the singleton Endpoints
*/
public static Endpoints getInstance(Validator validator, FHIRPathEvaluator evaluator, int port) {
teardownLoadingRoutes();
if (endpoints == null) {
endpoints = new Endpoints(validator, evaluator, port);
endpoints = new Endpoints(validator, evaluator);
}
return endpoints;
}

/**
* Creates the API routes for receiving and processing HTTP requests from clients.
* Creates the API routes for receiving and processing HTTP requests from
* clients.
*/
private void createRoutes() {
// This adds permissive CORS headers to all requests
before((req, res) -> {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS");
res.header("Access-Control-Allow-Headers", "*");
});

// This responds to OPTIONS requests, used by browsers to "preflight" check CORS requests,
// with a 200 OK response with no content and the CORS headers above
options("*", (req, res) -> "");

if (validator != null) {
ValidatorEndpoint.getInstance(validator);
Expand Down