Skip to content
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
65 changes: 32 additions & 33 deletions src/main/java/co/zeroae/gate/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.*;
import java.util.function.Supplier;

/**
* This class implements a GATE application using AWS Lambda.
Expand Down Expand Up @@ -54,10 +53,6 @@ public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatew
"Gate Load", App::loadApplication);
private static final AppMetadata metadata = loadMetadata();

private static final Map<String, DocumentExporter> exporters = AWSXRay.createSegment(
"Gate Exporters", Utils::loadExporters
);

private static final DocumentLRUCache cache = AWSXRay.createSegment("Cache Init",
() -> new DocumentLRUCache(App.CACHE_DIR, App.CACHE_DIR_USAGE));

Expand All @@ -70,7 +65,7 @@ public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent in
else if (path.matches("^/([^/]*)/metadata/?$"))
return handleMetadata(input, context);
else
throw new RuntimeException("How did you get here?");
throw new RuntimeException("Unexpected path expression " + path);
}

public APIGatewayProxyResponseEvent handleMetadata(APIGatewayProxyRequestEvent input, final Context context) {
Expand All @@ -90,37 +85,29 @@ public APIGatewayProxyResponseEvent handleMetadata(APIGatewayProxyRequestEvent i
public APIGatewayProxyResponseEvent handleExecute(APIGatewayProxyRequestEvent input, final Context context) {
final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
.withHeaders(new HashMap<>());
final Map<String, String> queryStringParams = Optional.ofNullable(input.getQueryStringParameters()).orElse(new HashMap<>());
final Map<String, List<String>> mQueryStringParams = Optional.ofNullable(input.getMultiValueQueryStringParameters()).orElse(new HashMap<>());
final Map<String, String> headers = input.getHeaders();
final Map<String, String> queryStringParams = Optional.ofNullable(
input.getQueryStringParameters()).orElse(new HashMap<>());
final Map<String, List<String>> mQueryStringParams = Optional.ofNullable(
input.getMultiValueQueryStringParameters()).orElse(new HashMap<>());
try {
final String acceptHeader = input.getHeaders().getOrDefault("Accept", "application/json");
final String responseType = ((Supplier<String>) () -> {
for (String mimeType : acceptHeader.split(",")) {
if (exporters.containsKey(mimeType.trim()))
return mimeType.trim();
else if (exporters.containsKey(mimeType.split(";")[0].trim()))
return mimeType.split(";")[0].trim();
}
return null;
}).get();
if (responseType != null)
response.getHeaders().put("Content-Type", responseType.split(";")[0].trim());

final DocumentExporter exporter = exporters.get(responseType);
if (exporter == null)
throw new IOException("Unsupported response content type.");

final String responseType = Utils.ensureValidResponseType(headers.getOrDefault(
"Accept", "application/json"));
final DocumentExporter exporter = Utils.exporters.get(responseType);
response.getHeaders().put("Content-Type", responseType.split(";")[0].trim());

final FeatureMap featureMap = Factory.newFeatureMap();
final Integer nextAnnotationId = Integer.parseInt(queryStringParams.getOrDefault("nextAnnotationId", "0"));
final String contentType = input.getHeaders().getOrDefault("Content-Type", "text/plain");
final Integer nextAnnotationId = Integer.parseInt(queryStringParams.getOrDefault(
"nextAnnotationId", "0"));
final String contentType = Utils.ensureValidRequestContentType(headers.getOrDefault(
"Content-Type", "text/plain"));
final String contentDigest = AWSXRay.createSubsegment("Message Digest",() -> {
String rv = Utils.computeMessageDigest(contentType + input.getBody() + nextAnnotationId + DIGEST_SALT);
AWSXRay.getCurrentSubsegment().putMetadata("SHA256", rv);
return rv;
});
featureMap.put("nextAnnotationId", nextAnnotationId);
putRequestBody(featureMap, contentType, contentDigest, input.getBody(), input.getIsBase64Encoded());
featureMapPutContent(featureMap, contentType, contentDigest, input.getBody(), input.getIsBase64Encoded());

response.getHeaders().put("x-zae-gate-cache", "HIT");
final Document doc = cache.computeIfNull(contentDigest, () -> {
Expand All @@ -142,18 +129,30 @@ public APIGatewayProxyResponseEvent handleExecute(APIGatewayProxyRequestEvent in
logger.error(e);
AWSXRay.getCurrentSubsegmentOptional().ifPresent((segment -> segment.addException(e)));
response.getHeaders().put("Content-Type", "application/json");
return response.withStatusCode(400).withBody(String.format(
"{\"message\":\"%s\"}", e.getMessage()));
return response.withStatusCode(400).withBody(Utils.asJson(
new HashMap<String, Object>() {{
put("message", e.getMessage());
}}
));
} catch (IOException e) {
logger.error(e);
AWSXRay.getCurrentSubsegmentOptional().ifPresent((segment -> segment.addException(e)));
response.getHeaders().put("Content-Type", "application/json");
return response.withStatusCode(406).withBody(String.format(
"{\"message\":\"%s\"}", e.getMessage()));
return response.withStatusCode(406).withBody(Utils.asJson(
new HashMap<String, Object>() {{
put("message", e.getMessage());
}}
));
}
}

private void putRequestBody(FeatureMap featureMap, String mimeType, String contentDigest, String content, boolean isBase64Encoded) throws MalformedURLException {
private void featureMapPutContent(
FeatureMap featureMap,
String mimeType,
String contentDigest,
String content,
boolean isBase64Encoded
) throws MalformedURLException {
featureMap.put(Document.DOCUMENT_MIME_TYPE_PARAMETER_NAME, mimeType);
if (!isBase64Encoded)
featureMap.put(Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME, content);
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/co/zeroae/gate/Utils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package co.zeroae.gate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import gate.*;
import gate.corpora.*;
import gate.corpora.export.GATEJsonExporter;
Expand All @@ -18,6 +20,44 @@

public class Utils {

static final ObjectMapper objectMapper = new ObjectMapper();

static final Map<String, DocumentExporter> exporters = loadExporters();

static String asJson(Map<String, Object> obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException jsonProcessingException) {
throw new RuntimeException(jsonProcessingException);
}
}

static String ensureValidRequestContentType(String contentType) throws GateException {
final String rv = contentType.equals("application/json") ? "text/json" : contentType;
if (!DocumentFormat.getSupportedMimeTypes().contains(rv)) {
throw new GateException(
"Unsupported MIME type " + contentType + " valid options are "
+ Arrays.toString(DocumentFormat.getSupportedMimeTypes()
.stream()
.map((type) -> type.equals("text/json") ? "application/json" : type)
.sorted()
.toArray())
);
}
return rv;
}

static String ensureValidResponseType(String acceptHeader) throws GateException {
for (String mimeType : acceptHeader.split(",")) {
if (exporters.containsKey(mimeType.trim()))
return mimeType.trim();
else if (exporters.containsKey(mimeType.split(";")[0].trim()))
return mimeType.split(";")[0].trim();
}
throw new GateException("Unsupported MIME response type " + acceptHeader + ", valid options are " +
Arrays.toString(exporters.keySet().stream().sorted().toArray()));
}

@FunctionalInterface
interface GATESupplier<T> {
T get() throws GateException;
Expand Down
8 changes: 4 additions & 4 deletions src/test/java/co/zeroae/gate/AppTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import com.fasterxml.jackson.core.JsonToken;
import com.sun.xml.fastinfoset.stax.StAXDocumentParser;
import gate.Document;
import gate.DocumentFormat;
import gate.util.GateException;
import org.apache.commons.codec.binary.Base64InputStream;
import org.junit.After;
import org.junit.Before;
Expand Down Expand Up @@ -201,19 +201,19 @@ public void testCache() {
}

@Test
public void testInputTypes() {
public void testInputTypes() throws GateException {
String[] types = {
"application/fastinfoset",
"application/json",
"text/html",
"text/json",
"text/plain",
"text/xml",
"text/x-cochrane",
"text/x-mediawiki",
"text/x-json-datasift",
"text/x-json-twitter",
};
for (String type: types) assertNotNull(DocumentFormat.getMimeTypeForString(type));
for (String type: types) assertNotNull(Utils.ensureValidRequestContentType(type));
}

@Test
Expand Down