diff --git a/src/main/java/co/zeroae/gate/AnnotationSetExporter.java b/src/main/java/co/zeroae/gate/AnnotationSetExporter.java index 3714b78..9e7d92e 100644 --- a/src/main/java/co/zeroae/gate/AnnotationSetExporter.java +++ b/src/main/java/co/zeroae/gate/AnnotationSetExporter.java @@ -9,7 +9,6 @@ import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; - import java.io.IOException; import java.io.OutputStream; @@ -47,6 +46,7 @@ static class GATEFastInfoset extends AnnotationSetExporter { public GATEFastInfoset() { super("AnnotationSet FastInfoset", "finf", "application/fastinfoset; includeText=no"); } + @Override public void export(Document doc, OutputStream out, FeatureMap options) throws IOException { try { @@ -60,6 +60,7 @@ public void export(Document doc, OutputStream out, FeatureMap options) throws IO static class GateXML extends AnnotationSetExporter { private static final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + /** * Creates a new exporter instance for a given file type with default * extension. diff --git a/src/main/java/co/zeroae/gate/App.java b/src/main/java/co/zeroae/gate/App.java index c88a1e3..fac05ec 100644 --- a/src/main/java/co/zeroae/gate/App.java +++ b/src/main/java/co/zeroae/gate/App.java @@ -5,21 +5,20 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; - import com.amazonaws.util.Base64; import com.amazonaws.xray.AWSXRay; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import gate.*; import gate.corpora.DocumentImpl; import gate.util.GateException; import gate.util.persistence.PersistenceManager; - import org.apache.log4j.LogManager; import org.apache.log4j.Logger; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLStreamHandler; @@ -44,20 +43,43 @@ public class App implements RequestHandler new DocumentLRUCache(App.CACHE_DIR, App.CACHE_DIR_USAGE)); - private static final URLStreamHandler b64Handler = new Handler(); + private static AppMetadata loadMetadata() { + final AppMetadata rv = new AppMetadata(); + // TODO: Load metadata/metadata.xml if it exists, and set as default values + rv.name = null; + rv.costPerRequest = Integer.parseInt(System.getenv().getOrDefault("GATE_APP_COST_PER_REQUEST", "0")); + rv.dailyQuota = Integer.parseUnsignedInt(System.getenv().getOrDefault("GATE_APP_DAILY_QUOTA", "0")); + rv.defaultAnnotations = System.getenv("GATE_APP_DEFAULT_ANNOTATIONS"); + rv.additionalAnnotations = System.getenv("GATE_APP_ADDITIONAL_ANNOTATIONS"); + return rv; + } + + private static CorpusController loadApplication() { + try { + final String gappResourcePah = GATE_APP_NAME + "/application.xgapp"; + final URL gappUrl = App.class.getClassLoader().getResource(gappResourcePah); + final File gappFile = new File(Objects.requireNonNull(gappUrl).getFile()); + final CorpusController rv = + (CorpusController) PersistenceManager.loadObjectFromFile(gappFile); + final Corpus corpus = Factory.newCorpus("Lambda Corpus"); + rv.setCorpus(corpus); + return rv; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, final Context context) { final String path = input.getPath(); if (path.matches("^/([^/]*)/?$")) @@ -84,7 +106,7 @@ public APIGatewayProxyResponseEvent handleMetadata(APIGatewayProxyRequestEvent i public APIGatewayProxyResponseEvent handleExecute(APIGatewayProxyRequestEvent input, final Context context) { final APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() - .withHeaders(new HashMap<>()); + .withHeaders(new HashMap<>()); final Map headers = input.getHeaders(); final Map queryStringParams = Optional.ofNullable( input.getQueryStringParameters()).orElse(new HashMap<>()); @@ -101,7 +123,7 @@ public APIGatewayProxyResponseEvent handleExecute(APIGatewayProxyRequestEvent in "nextAnnotationId", "0")); final String contentType = Utils.ensureValidRequestContentType(headers.getOrDefault( "Content-Type", "text/plain")); - final String contentDigest = AWSXRay.createSubsegment("Message Digest",() -> { + final String contentDigest = AWSXRay.createSubsegment("Message Digest", () -> { String rv = Utils.computeMessageDigest(contentType + input.getBody() + nextAnnotationId + DIGEST_SALT); AWSXRay.getCurrentSubsegment().putMetadata("SHA256", rv); return rv; @@ -176,7 +198,7 @@ private Document execute(FeatureMap docFeatureMap) throws GateException { // Note: The DocumentImpl API does not conform to JavaBeans for the nextAnnotationId method. // Paragraphs may be annotated right away, so we need to handle that issue. - final int nextAnnotationId = (Integer)docFeatureMap.get("nextAnnotationId"); + final int nextAnnotationId = (Integer) docFeatureMap.get("nextAnnotationId"); docFeatureMap.remove("nextAnnotationId"); rvImpl = (DocumentImpl) Factory.createResource("gate.corpora.DocumentImpl", docFeatureMap); rvImpl.setNextAnnotationId(Math.max(nextAnnotationId, rvImpl.getNextAnnotationId())); @@ -194,10 +216,10 @@ private Document execute(FeatureMap docFeatureMap) throws GateException { } /** - * @param exporter The document exporter - * @param doc an instance of gate.Document + * @param exporter The document exporter + * @param doc an instance of gate.Document * @param annotationSelector the List of AnnotationTypes to return - * @param response The response where we put the exported Document as body + * @param response The response where we put the exported Document as body * @return the modified response */ private APIGatewayProxyResponseEvent export( @@ -240,30 +262,4 @@ private APIGatewayProxyResponseEvent export( } return response; } - - private static AppMetadata loadMetadata() { - final AppMetadata rv = new AppMetadata(); - // TODO: Load metadata/metadata.xml if it exists, and set as default values - rv.name = null; - rv.costPerRequest = Integer.parseInt(System.getenv().getOrDefault("GATE_APP_COST_PER_REQUEST", "0")); - rv.dailyQuota = Integer.parseUnsignedInt(System.getenv().getOrDefault("GATE_APP_DAILY_QUOTA", "0")); - rv.defaultAnnotations = System.getenv("GATE_APP_DEFAULT_ANNOTATIONS"); - rv.additionalAnnotations = System.getenv("GATE_APP_ADDITIONAL_ANNOTATIONS"); - return rv; - } - - private static CorpusController loadApplication() { - try { - final String gappResourcePah = GATE_APP_NAME + "/application.xgapp"; - final URL gappUrl = App.class.getClassLoader().getResource(gappResourcePah); - final File gappFile = new File(Objects.requireNonNull(gappUrl).getFile()); - final CorpusController rv = - (CorpusController) PersistenceManager.loadObjectFromFile(gappFile); - final Corpus corpus = Factory.newCorpus("Lambda Corpus"); - rv.setCorpus(corpus); - return rv; - } catch (Exception e) { - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/co/zeroae/gate/AppMetadata.java b/src/main/java/co/zeroae/gate/AppMetadata.java index af74639..5adc280 100644 --- a/src/main/java/co/zeroae/gate/AppMetadata.java +++ b/src/main/java/co/zeroae/gate/AppMetadata.java @@ -1,4 +1,5 @@ package co.zeroae.gate; + /** * This class structure must match https://github.com/GateNLP/cloud-client/blob/master/library/src/main/java/uk/ac/gate/cloud/online/ServiceMetadata.java */ diff --git a/src/main/java/co/zeroae/gate/DocumentLRUCache.java b/src/main/java/co/zeroae/gate/DocumentLRUCache.java index 0eddc2e..c945bd5 100644 --- a/src/main/java/co/zeroae/gate/DocumentLRUCache.java +++ b/src/main/java/co/zeroae/gate/DocumentLRUCache.java @@ -26,6 +26,20 @@ class DocumentLRUCache { cache = initializeCache(cacheDir, maxUsage); } + private static DiskLruCache initializeCache(String cacheDir, double maxUsage) { + File cacheDirPath = new File(cacheDir); + if (!cacheDirPath.exists() && !cacheDirPath.mkdirs()) { + throw new RuntimeException("Unable to create cache directory '" + cacheDirPath.getName() + "'."); + } + for (File file : Objects.requireNonNull(cacheDirPath.listFiles())) file.delete(); + try { + long usableSpace = (long) (cacheDirPath.getUsableSpace() * maxUsage); + return DiskLruCache.open(cacheDirPath, VERSION, VALUE_COUNT, usableSpace); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public Document computeIfNull(String key, Utils.GATESupplier supplier) throws GateException { Document rv = get(key); if (rv == null) { @@ -74,18 +88,4 @@ public void put(String key, Document doc) { AWSXRay.endSubsegment(); } } - - private static DiskLruCache initializeCache(String cacheDir, double maxUsage) { - File cacheDirPath = new File(cacheDir); - if (!cacheDirPath.exists() && !cacheDirPath.mkdirs()) { - throw new RuntimeException("Unable to create cache directory '" + cacheDirPath.getName() + "'."); - } - for (File file: Objects.requireNonNull(cacheDirPath.listFiles())) file.delete(); - try { - long usableSpace = (long) (cacheDirPath.getUsableSpace()*maxUsage); - return DiskLruCache.open(cacheDirPath, VERSION, VALUE_COUNT, usableSpace); - } catch (IOException e) { - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/co/zeroae/gate/Utils.java b/src/main/java/co/zeroae/gate/Utils.java index aab2c52..d43fb76 100644 --- a/src/main/java/co/zeroae/gate/Utils.java +++ b/src/main/java/co/zeroae/gate/Utils.java @@ -11,7 +11,9 @@ import gate.util.GateException; import org.codehaus.httpcache4j.util.Hex; -import javax.xml.stream.*; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import java.io.Reader; import java.net.MalformedURLException; import java.security.MessageDigest; @@ -42,7 +44,7 @@ static String ensureValidRequestContentType(String contentType) throws GateExcep .map((type) -> type.equals("text/json") ? "application/json" : type) .sorted() .toArray()) - ); + ); } return rv; } @@ -58,17 +60,11 @@ static String ensureValidResponseType(String acceptHeader) throws GateException Arrays.toString(exporters.keySet().stream().sorted().toArray())); } - @FunctionalInterface - interface GATESupplier { - T get() throws GateException; - } - /** - * * @param gateXMLReader a Reader with GateXML content. * @return The parsed Document. * @throws ResourceInstantiationException if the Factory fails to create an empty Document. - * @throws XMLStreamException if the reader has invalid XML content. + * @throws XMLStreamException if the reader has invalid XML content. */ static Document xmlToDocument(Reader gateXMLReader) throws ResourceInstantiationException, XMLStreamException { final Document doc = Factory.newDocument(""); @@ -76,28 +72,11 @@ static Document xmlToDocument(Reader gateXMLReader) throws ResourceInstantiation reader = XMLInputFactory.newFactory().createXMLStreamReader(gateXMLReader); do { reader.next(); - } while(reader.getEventType() != XMLStreamReader.START_ELEMENT); + } while (reader.getEventType() != XMLStreamReader.START_ELEMENT); gate.corpora.DocumentStaxUtils.readGateXmlDocument(reader, doc); return doc; } - /** - * The Plugin.Component class has a bug when the baseUrl fails to resolve uniquely. - * This fixes the bug by assigning the Class' hashCode instead of the Plugin level one. - */ - private static class UniqueHashComponent extends Plugin.Component { - final private int hashCode; - public UniqueHashComponent(Class resourceClass) throws MalformedURLException { - super(resourceClass); - hashCode = resourceClass.hashCode(); - } - - @Override - public int hashCode() { - return hashCode; - } - } - static void loadDocumentFormats() { try { final Set> classes = new HashSet<>(); @@ -108,7 +87,7 @@ static void loadDocumentFormats() { classes.add(JSONTweetFormat.class); classes.add(MediaWikiDocumentFormat.class); classes.add(PubmedTextDocumentFormat.class); - for (Class clazz: classes) { + for (Class clazz : classes) { Gate.getCreoleRegister().registerPlugin(new UniqueHashComponent(clazz)); } } catch (GateException | MalformedURLException e) { @@ -118,6 +97,7 @@ static void loadDocumentFormats() { /** * Loads all exporters that we support + * * @return an UnmodifiableMap of the supported exporters. */ static Map loadExporters() { @@ -150,4 +130,27 @@ static String computeMessageDigest(String message) { throw new RuntimeException(e); } } + + @FunctionalInterface + interface GATESupplier { + T get() throws GateException; + } + + /** + * The Plugin.Component class has a bug when the baseUrl fails to resolve uniquely. + * This fixes the bug by assigning the Class' hashCode instead of the Plugin level one. + */ + private static class UniqueHashComponent extends Plugin.Component { + final private int hashCode; + + public UniqueHashComponent(Class resourceClass) throws MalformedURLException { + super(resourceClass); + hashCode = resourceClass.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + } } diff --git a/src/main/java/co/zeroae/gate/b64/Handler.java b/src/main/java/co/zeroae/gate/b64/Handler.java index 8c7131a..ae416df 100644 --- a/src/main/java/co/zeroae/gate/b64/Handler.java +++ b/src/main/java/co/zeroae/gate/b64/Handler.java @@ -15,6 +15,12 @@ public class Handler extends URLStreamHandler { static final public Map paths = Collections.synchronizedMap(new WeakHashMap<>()); + + @Override + protected URLConnection openConnection(URL u) { + return new Connection(u); + } + private class Connection extends URLConnection { /** * Constructs a URL connection to the specified URL. A connection to @@ -25,6 +31,7 @@ private class Connection extends URLConnection { protected Connection(URL url) { super(url); } + @Override public void connect() { } @@ -41,8 +48,4 @@ public InputStream getInputStream() throws IOException { )); } } - @Override - protected URLConnection openConnection(URL u) { - return new Connection(u); - } }