Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/.vscode/settings.json
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/.vscode/settings.json	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/.vscode/settings.json	(revision 35733)
@@ -0,0 +1,5 @@
+{
+    "java.project.referencedLibraries": [
+        "lib/**/*.jar"
+    ]
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/README.md
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/README.md	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/README.md	(revision 35733)
@@ -0,0 +1,30 @@
+# Installation
+
+Find the path to your tesseract binaries directory, then run
+
+```
+./setup.sh
+```
+
+# Now edit:
+
+  src/main/webapp/webContent/unauthorizsed.html
+
+so the button displays the name Atea, and points to the https://atea.space website
+
+
+# Compile up and install the war file
+
+  ant install
+
+
+# Now update apache2 config with ProxyPass rules
+
+ sudo emacs /etc/apache2/sites-enabled/000-default-le-ssl.conf
+
+   ProxyPass /gs3-atea-ocr http://localhost:8383/gs3-atea-ocr
+   ProxyPassReverse /gs3-atea-ocr http://localhost:8383/gs3-atea-ocr
+
+#Same for the http version:
+
+ sudo emacs /etc/apache2/sites-enabled/000-default.conf
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/build.xml
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/build.xml	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/build.xml	(revision 35733)
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project name="gs3-atea-ocr-ext" default="usage" basedir=".">
+    <property environment="env"/>
+    <property name="gsdl3.src.home" value="${env.GSDL3SRCHOME}"/>
+    <property name="src.home" value="${basedir}/src/main/java" />
+    <property name="build.home" value="${basedir}/build" />
+    <property name="web.dir" value="${basedir}/src/main/webapp" />
+    <property name="tomcat.webapps.dir" value="${gsdl3.src.home}/packages/tomcat/webapps" />
+    
+    <path id="compile.classpath">
+        <fileset dir="lib/java">
+            <include name="*.jar" />
+        </fileset>
+    </path>
+    
+    <!-- Targets -->
+    <target name="usage" description="Print a help message">
+        <echo message="Execute 'ant -projecthelp' for a list of targets." />
+        <echo message="Execute 'ant -help' for Ant help." />
+	</target>
+
+    <target name="file-checks" description="Ensures that required external files exist.">
+        <fail message="config.properties was not found. Please run ${basedir}/setup.sh">
+            <condition>
+                <not>
+                    <available file="${basedir}/config.properties"></available>
+                </not>
+            </condition>
+        </fail>
+     </target>
+	
+	<target name="compile" description="Compile any java classes.">
+	    <property name="build.classes" value="${build.home}/classes" />
+	
+	    <!-- Reset the build directory -->
+	    <delete dir="${build.home}" />
+	    <mkdir dir="${build.home}" />
+	    <mkdir dir="${build.classes}" />
+	    
+	    <!-- Compile all java files -->
+        <javac srcdir="${src.home}" destdir="${build.classes}" debug="true">
+            <classpath>
+                <path refid="compile.classpath" />
+            </classpath>
+        </javac>
+    </target>
+    
+    <target name="package" depends="file-checks,compile" description="Create a WAR package.">
+        <echo message="Creating ${build.home}/gs3-atea-ocr.war" />
+            
+        <war destfile="${build.home}/gs3-atea-ocr.war" webxml="${web.dir}/WEB-INF/web.xml">
+            <fileset dir="${web.dir}/webContent" includes="**" />
+            <fileset file="${basedir}/config.properties" />
+        
+            <lib dir="lib/java" />
+            <classes dir="${build.classes}" />
+
+            <webinf dir="${web.dir}/WEB-INF" />
+        </war>
+    </target>
+    
+    <target name="install" depends="package" description="Installs the WAR package into tomcat.">
+        <!-- Check that gs3-setup has been run -->
+        <fail message="Please run 'gs3-setup' (Windows) or 'source gs3-setup.sh' (Linux/Mac) in the Greenstone3 installation that's associated with this installation before running this step.">
+            <condition>
+                <not>
+                    <isset property="env.GSDL3HOME" />
+                </not>
+            </condition>
+        </fail>
+        
+        <echo message="Copying war package to ${tomcat.webapps.dir}" />
+        <copy file="${build.home}/gs3-atea-ocr.war" todir="${tomcat.webapps.dir}" />
+        <echo message="Done! Tomcat may take a few seconds to recognise the changes." />
+    </target>
+</project>
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/config.properties.in
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/config.properties.in	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/config.properties.in	(revision 35733)
@@ -0,0 +1,4 @@
+# Config file for the Atea OCR Servlet
+
+# Absolute path to the tesseract data directory
+tesseract.data.path=@tess.data@
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/lib/java/gson-LICENSE
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/lib/java/gson-LICENSE	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/lib/java/gson-LICENSE	(revision 35733)
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/setup.sh
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/setup.sh	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/setup.sh	(revision 35733)
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+if [ ! -f config.properties ] ; then
+    echo "Generating config.properties from config.properties.in."
+    cp config.properties.in config.properties
+
+    read -p "Enter the path to your Tesseract data directory: " tessdata
+    awk '{sub("@tess.data@","'${tessdata}'")}1' config.properties > config.temp && mv config.temp config.properties
+else
+    echo "config.properties already exists; no changes made."
+fi
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/Exceptions/LeptonicaException.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/Exceptions/LeptonicaException.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/Exceptions/LeptonicaException.java	(revision 35733)
@@ -0,0 +1,15 @@
+package org.atea.nlptools.ocr.Exceptions;
+
+/**
+ * Represents an error that occured while interacting with the Leptonica library.
+ */
+public class LeptonicaException extends Exception
+{
+    /**
+     * Initializes a new instance of the {@link LeptonicaException} class.
+     * @param message A message detailing the exception.
+     */
+    public LeptonicaException(String message) {
+        super(message);
+    }
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/Exceptions/TesseractException.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/Exceptions/TesseractException.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/Exceptions/TesseractException.java	(revision 35733)
@@ -0,0 +1,15 @@
+package org.atea.nlptools.ocr.Exceptions;
+
+/**
+ * Represents an error that occured while interacting with the Tesseract library.
+ */
+public class TesseractException extends Exception
+{
+    /**
+     * Initializes a new instance of the {@link TesseractException} class.
+     * @param message A message detailing the exception.
+     */
+    public TesseractException(String message) {
+        super(message);
+    }
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/HttpStatusCode.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/HttpStatusCode.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/HttpStatusCode.java	(revision 35733)
@@ -0,0 +1,22 @@
+package org.atea.nlptools.ocr.abstractions.objects;
+
+public class HttpStatusCode
+{
+    /**
+     * 403 Forbidden
+     * The client does not have access rights to the requested content.
+     */
+    public static final int ClientForbidden = 403;
+
+    /**
+     * 413 Payload Too Large
+     * The request is larger than limits defined by the server.
+     */
+    public static final int ClientPayloadTooLarge = 413;
+
+    /**
+     * 500 Internal Server Error
+     * The server has encountered a situation that it does not know how to handle.
+     */
+    public static final int ServerInternalServerError = 500;
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/IOcrFile.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/IOcrFile.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/IOcrFile.java	(revision 35733)
@@ -0,0 +1,27 @@
+package org.atea.nlptools.ocr.abstractions.objects;
+
+import java.io.File;
+
+/**
+ * Represents a temporary file to be OCR'ed.
+ */
+public interface IOcrFile
+{
+    /**
+     * Gets the unique key of this file.
+     * @return
+     */
+    String getKey();
+
+    /**
+     * Gets the name of the originally submitted file.
+     * @return
+     */
+    String getFileName();
+
+    /**
+     * Gets the temporary disk file to be OCR'ed.
+     * @return
+     */
+    File getTempFile();
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/IOcrOptions.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/IOcrOptions.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/objects/IOcrOptions.java	(revision 35733)
@@ -0,0 +1,19 @@
+package org.atea.nlptools.ocr.abstractions.objects;
+
+/**
+ * Represents a marker interface for OCR options.
+ */
+public interface IOcrOptions
+{
+    /**
+     * Gets the language that should be used.
+     * @return The language string.
+     */
+    String getLanguage();
+
+    /**
+     * Sets the language that should be used.
+     * @param value The language.
+     */
+    void setLanguage(String value);
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/services/IOcrService.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/services/IOcrService.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/abstractions/services/IOcrService.java	(revision 35733)
@@ -0,0 +1,19 @@
+package org.atea.nlptools.ocr.abstractions.services;
+
+import java.io.File;
+
+import org.atea.nlptools.ocr.abstractions.objects.IOcrOptions;
+
+/**
+ * Represents a service for performing OCR on files.
+ */
+public interface IOcrService
+{
+    /**
+     * Runs optical character recognition on a file.
+     * @param file The file to perform OCR on.
+     * @param language The language/s of the file. Separate multiple with a '+'.
+     * @return The recognised content of the file.
+     */
+    String run(File file, IOcrOptions options) throws Exception;
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/OcrFile.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/OcrFile.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/OcrFile.java	(revision 35733)
@@ -0,0 +1,43 @@
+package org.atea.nlptools.ocr.objects;
+
+import java.io.File;
+
+import org.atea.nlptools.ocr.abstractions.objects.IOcrFile;
+
+/**
+ * Represents a temporary file to be OCR'ed.
+ */
+public class OcrFile implements IOcrFile
+{
+    private String key;
+    private String fileName;
+    private File tempFile;
+
+    /**
+     * Initializes a new instance of the {@link OcrFile} class.
+     * @param key The unique key of this file.
+     * @param fileName The name of the original submitted file.
+     * @param tempFile The temporary disk file to be OCR'ed.
+     */
+    public OcrFile(String key, String fileName, File tempFile)
+    {
+        this.key = key;
+        this.fileName = fileName;
+        this.tempFile = tempFile;
+    }
+
+    @Override
+    public String getKey() {
+        return this.key;
+    }
+
+    @Override
+    public String getFileName() {
+        return this.fileName;
+    }
+
+    @Override
+    public File getTempFile() {
+        return this.tempFile;
+    }
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/OcrOptionsDto.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/OcrOptionsDto.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/OcrOptionsDto.java	(revision 35733)
@@ -0,0 +1,6 @@
+package org.atea.nlptools.ocr.objects;
+
+public class OcrOptionsDto
+{
+    public boolean layoutDetection;
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/TesseractOptions.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/TesseractOptions.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/objects/TesseractOptions.java	(revision 35733)
@@ -0,0 +1,30 @@
+package org.atea.nlptools.ocr.objects;
+
+import org.atea.nlptools.ocr.abstractions.objects.IOcrOptions;
+import org.bytedeco.tesseract.global.tesseract;
+
+/**
+ * Represents various options that can be used when performing OCR using Tesseract.
+ */
+public class TesseractOptions implements IOcrOptions
+{
+    private String language;
+
+    public int pageSegmentationMode;
+
+    public TesseractOptions()
+    {
+        this.language = "mri";
+        this.pageSegmentationMode = tesseract.PSM_AUTO;
+    }
+
+    @Override
+    public String getLanguage() {
+        return this.language;
+    }
+
+    @Override
+    public void setLanguage(String value) {
+        this.language = value;
+    }
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/services/TesseractOcrService.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/services/TesseractOcrService.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/services/TesseractOcrService.java	(revision 35733)
@@ -0,0 +1,85 @@
+package org.atea.nlptools.ocr.services;
+
+import java.io.File;
+
+import org.atea.nlptools.ocr.Exceptions.LeptonicaException;
+import org.atea.nlptools.ocr.Exceptions.TesseractException;
+import org.atea.nlptools.ocr.abstractions.objects.IOcrOptions;
+import org.atea.nlptools.ocr.abstractions.services.IOcrService;
+import org.atea.nlptools.ocr.objects.TesseractOptions;
+
+import org.bytedeco.javacpp.BytePointer;
+import org.bytedeco.leptonica.PIX;
+import org.bytedeco.leptonica.global.lept;
+import org.bytedeco.tesseract.TessBaseAPI;
+
+/**
+ * Represents a service for performing OCR on files using the Tesseract engine.
+ */
+public class TesseractOcrService implements IOcrService
+{
+    private final String tessData;
+
+    /**
+     * Initializes a new instance of the {@link TesseractOcrService} class.
+     * @param tesseractDataPath Path to the tesseract data directory.
+     */
+    public TesseractOcrService(String tesseractDataPath)
+    {
+        this.tessData = tesseractDataPath;
+    }
+
+    @Override
+    public String run(File file, IOcrOptions options)
+        throws Exception, IllegalArgumentException
+    {
+        if (options.getClass() != TesseractOptions.class)
+        {
+            throw new IllegalArgumentException("Expected an options parameter of type " + TesseractOptions.class);
+        }
+
+        TesseractOptions tOptions = (TesseractOptions)options;
+        PIX inputImage = null;
+        TessBaseAPI api = null;
+        BytePointer outputTextPtr = null;
+
+        try
+        {
+            inputImage = lept.pixRead(file.getAbsolutePath());
+            if (inputImage == null) {
+                throw new LeptonicaException("Didn't recognise the image format. Check stdout for more info.");
+            }
+
+            api = new TessBaseAPI();
+
+            if (api.Init(this.tessData, tOptions.getLanguage()) != 0) {
+                throw new TesseractException("Could not initialize tesseract. Check stdout for more info");
+            }
+
+            api.SetPageSegMode(tOptions.pageSegmentationMode);
+            api.SetImage(inputImage);
+            outputTextPtr = api.GetUTF8Text();
+
+            return outputTextPtr.getString();
+        }
+        finally
+        {
+            if (inputImage != null)
+            {
+                lept.pixDestroy(inputImage);
+            }
+
+            if (api != null)
+            {
+                api.End();
+                api.close();
+            }
+
+            if (outputTextPtr != null)
+            {
+                outputTextPtr.deallocate();
+                outputTextPtr.close();
+            }
+        }
+    }
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/servlets/TesseractServlet.java
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/servlets/TesseractServlet.java	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/java/org/atea/nlptools/ocr/servlets/TesseractServlet.java	(revision 35733)
@@ -0,0 +1,217 @@
+package org.atea.nlptools.ocr.servlets;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.MultipartConfig;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.Part;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonWriter;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import org.atea.nlptools.ocr.abstractions.objects.HttpStatusCode;
+import org.atea.nlptools.ocr.abstractions.objects.IOcrFile;
+import org.atea.nlptools.ocr.abstractions.objects.IOcrOptions;
+import org.atea.nlptools.ocr.abstractions.services.IOcrService;
+import org.atea.nlptools.ocr.objects.OcrFile;
+import org.atea.nlptools.ocr.objects.OcrOptionsDto;
+import org.atea.nlptools.ocr.objects.TesseractOptions;
+import org.atea.nlptools.ocr.services.TesseractOcrService;
+import org.bytedeco.tesseract.global.tesseract;
+
+@MultipartConfig
+public class TesseractServlet extends HttpServlet
+{
+	/**
+	 * Gets the maximum number of parts that may be submitted in any one request.
+	 */
+	private static final int MaxParts = 10;
+
+	/**
+	 * Gets the maximum size in bytes that a part may be.
+	 */
+	private static final int MaxPartSize = 1024 * 1024 * 50;
+
+	private static final Logger logger = LogManager.getLogger(TesseractServlet.class);
+	private static final Type optionsTypeToken = new TypeToken<HashMap<String, OcrOptionsDto>>() {}.getType();
+
+	private final Gson gsonInstance;
+
+	private IOcrService ocrService;
+
+	public TesseractServlet()
+	{
+		gsonInstance = new Gson();
+	}
+
+	@Override
+	public void init() 
+	{
+		Properties prop = new Properties();
+
+		try
+		{
+			FileInputStream fis = new FileInputStream("../webapps/gs3-atea-ocr/config.properties");
+			prop.load(fis);
+			fis.close();
+		}
+		catch (IOException ex) 
+		{
+			logger.error("Failed to retrieve properties file", ex);
+		}
+
+		String tessBin = prop.getProperty("tesseract.data.path");
+		this.ocrService = new TesseractOcrService(tessBin);
+
+		logger.info("Initialised!");
+	}
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+		throws IOException
+	{
+		response.sendError(HttpStatusCode.ClientForbidden, "POST Multipart request expected.");
+	}
+	
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		try
+		{
+			logger.trace("POST request received.");
+			final UUID requestUUID = UUID.randomUUID();
+
+			final TesseractOptions genericOptions = new TesseractOptions();
+			HashMap<String, IOcrOptions> options = ParseOptions(request);
+			
+			final List<IOcrFile> files = CreatePartTempFiles(request.getParts(), requestUUID, response);
+			if (files == null) {
+				return;
+			}
+
+			response.setContentType("application/json; charset=UTF-8");
+			JsonWriter writer = gsonInstance.newJsonWriter(response.getWriter());
+			writer.beginArray();
+
+			for (IOcrFile file : files)
+			{
+				IOcrOptions actualOptions = options.get(file.getKey());
+				if (actualOptions == null) {
+					actualOptions = genericOptions;
+				}
+
+				String ocrOutput = ocrService.run(file.getTempFile(), actualOptions);
+
+				writer.beginObject();
+
+				writer.name("key");
+				writer.value(file.getKey());
+
+				writer.name("fileName");
+				writer.value(file.getFileName());
+
+				writer.name("text");
+				writer.value(ocrOutput);
+
+				writer.endObject();
+			}
+
+			writer.endArray();
+			writer.flush();
+		}
+		catch (Exception ex)
+		{
+			response.sendError(HttpStatusCode.ServerInternalServerError, "Failed to process the request.");
+			logger.error("Failed to complete API call", ex);
+		}
+	}
+	
+	/**
+	 * Saves the parts in a request to temporary files. 
+	 * @param parts The parts.
+	 * @param requestUUID The assigned UUID of the request.
+	 * @param response The response.
+	 * @return The list of temporary files, or <code>null</code> if the operation failed.
+	 * @throws IOException
+	 * @throws ServletException
+	 */
+	private List<IOcrFile> CreatePartTempFiles(Collection<Part> parts, UUID requestUUID, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (parts.size() > MaxParts)
+		{
+			response.sendError(HttpStatusCode.ClientPayloadTooLarge, "No more than " + MaxParts + " parts may be submitted.");
+			return null;
+		}
+
+		ArrayList<IOcrFile> files = new ArrayList<>();
+
+		for (Part p : parts)
+		{
+			if (p.getSubmittedFileName() == null) {
+				continue;
+			}
+
+			if (p.getSize() > MaxPartSize)
+			{
+				response.sendError(HttpStatusCode.ClientPayloadTooLarge, "A submitted part must be no more than " + MaxPartSize + " bytes");
+				return null;
+			}
+
+			File tempFile = File.createTempFile("atea-ocr", requestUUID.toString());
+			p.write(tempFile.getAbsolutePath());
+
+			files.add(new OcrFile(p.getName(), p.getSubmittedFileName(), tempFile));
+		}
+		
+		return files;
+	}
+
+	/**
+	 * Parses the options parameter.
+	 * @param request The request.
+	 * @return The submitted options.
+	 */
+	private HashMap<String, IOcrOptions> ParseOptions(HttpServletRequest request)
+	{
+		HashMap<String, IOcrOptions> ret = new HashMap<>();
+
+		String options = request.getParameter("options");
+		if (options == null) {
+			return ret;
+		}
+
+		HashMap<String, OcrOptionsDto> dtoOptions = gsonInstance.fromJson(options, optionsTypeToken);
+
+		for (String key : dtoOptions.keySet())
+		{
+			OcrOptionsDto dto = dtoOptions.get(key);
+			TesseractOptions tOptions = new TesseractOptions();
+
+			if (dto.layoutDetection) {
+				tOptions.pageSegmentationMode = tesseract.PSM_AUTO_OSD;
+			}
+
+			ret.put(key, tOptions);
+		}
+		
+		return ret;
+	}
+}
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/WEB-INF/log4j2.xml
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/WEB-INF/log4j2.xml	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/WEB-INF/log4j2.xml	(revision 35733)
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="warn" name="AteaOcr" packages="">
+  <Appenders>
+    <File name="FileOutput" fileName="../logs/gs3-atea-ocr.log" append="true">
+      <PatternLayout>
+        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
+      </PatternLayout>
+    </File>
+  </Appenders>
+  <Loggers>
+    <Root level="trace">
+      <AppenderRef ref="FileOutput"/>
+    </Root>
+  </Loggers>
+</Configuration>
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/WEB-INF/web.xml
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/WEB-INF/web.xml	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/WEB-INF/web.xml	(revision 35733)
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+                             http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+
+    <!-- General description of your web application -->
+    <display-name>Atea OCR Servlet</display-name>
+    <description>
+        A servlet that provides OCR capabilities for reo MÄori documents.
+    </description>
+
+    <!-- Context initialization parameters that define shared
+         String constants used within your application, which
+         can be customized by the system administrator who is
+         installing your application.  The values actually
+         assigned to these parameters can be retrieved in a
+         servlet or JSP page by calling:
+
+             String value =
+               getServletContext().getInitParameter("name");
+
+         where "name" matches the <param-name> element of
+         one of these initialization parameters.
+
+         You can define any number of context initialization
+         parameters, including zero.
+    -->
+
+    <filter>
+        <filter-name>CorsFilter</filter-name>
+        <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+        <init-param>
+          <param-name>cors.allowed.origins</param-name>
+          <param-value>http://localhost:8080</param-value> <!-- Separate values by a comma -->
+        </init-param>
+    </filter>
+
+    <!-- Servlet definitions for the servlets that make up
+         your web application, including initialization
+         parameters.  With Tomcat, you can also send requests
+         to servlets not listed here with a request like this:
+
+           http://localhost:8080/{context-path}/servlet/{classname}
+
+         but this usage is not guaranteed to be portable.  It also
+         makes relative references to images and other resources
+         required by your servlet more complicated, so defining
+         all of your servlets (and defining a mapping to them with
+         a servlet-mapping element) is recommended.
+
+         Servlet initialization parameters can be retrieved in a
+         servlet or JSP page by calling:
+
+             String value =
+               getServletConfig().getInitParameter("name");
+
+         where "name" matches the <param-name> element of
+         one of these initialization parameters.
+
+         You can define any number of servlets, including zero.
+    -->
+
+    <servlet>
+      <servlet-name>tesseract</servlet-name>
+      <servlet-class>org.atea.nlptools.ocr.servlets.TesseractServlet</servlet-class>
+      <load-on-startup>0</load-on-startup>
+    </servlet>
+
+    <!-- Define mappings that are used by the servlet container to
+         translate a particular request URI (context-relative) to a
+         particular servlet.  The examples below correspond to the
+         servlet descriptions above.  Thus, a request URI like:
+
+           http://localhost:8080/{contextpath}/graph
+
+         will be mapped to the "graph" servlet, while a request like:
+
+           http://localhost:8080/{contextpath}/saveCustomer.do
+
+         will be mapped to the "controller" servlet.
+
+         You may define any number of servlet mappings, including zero.
+         It is also legal to define more than one mapping for the same
+         servlet, if you wish to.
+    -->
+
+    <filter-mapping>
+      <filter-name>CorsFilter</filter-name>
+      <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+    <servlet-mapping>
+      <servlet-name>tesseract</servlet-name>
+      <url-pattern>/tesseract</url-pattern>
+    </servlet-mapping>
+
+    <!-- Define the default session timeout for your application,
+         in minutes.  From a servlet or JSP page, you can modify
+         the timeout for a particular session dynamically by using
+         HttpSession.getMaxInactiveInterval(). -->
+
+    <session-config>
+      <session-timeout>30</session-timeout>    <!-- 30 minutes -->
+    </session-config>
+
+    <welcome-file-list>
+      <welcome-file>unauthorised.html</welcome-file>
+    </welcome-file-list>
+</web-app>
Index: /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/webContent/unauthorised.html
===================================================================
--- /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/webContent/unauthorised.html	(revision 35733)
+++ /gs3-extensions/atea-nlp-tools/trunk/src/ocr/src/main/webapp/webContent/unauthorised.html	(revision 35733)
@@ -0,0 +1,88 @@
+<html>
+    <head>
+        <title>Unauthorised</title>
+
+        <meta charset="utf-8" />
+
+        <link rel="preconnect" href="https://fonts.googleapis.com" />
+        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+        <link href="https://fonts.googleapis.com/css2?family=Oswald&display=swap" rel="stylesheet" />
+
+        <style>
+            body {
+                background-color: rgb(51, 6, 31);
+                font-family: 'Roboto', sans-serif;
+                margin: 0;
+            }
+
+            .container {
+                display: -webkit-box;
+                display: -ms-flexbox;
+                display: flex;
+
+                -webkit-box-pack: center;
+                -ms-flex-pack: center;
+                justify-content: center;
+
+                -webkit-box-align: center;
+                -ms-flex-align: center;
+                align-items: center;
+
+                height: 100vh;
+            }
+
+            .cover {
+                display: -webkit-box;
+                display: -ms-flexbox;
+                display: flex;
+                
+                -webkit-box-pack: center;
+                -ms-flex-pack: center;
+                justify-content: center;
+
+                -webkit-box-align: center;
+                -ms-flex-align: center;
+                align-items: center;
+
+                padding: 2em 4em;
+                border-radius: 10px 30px;
+                border: 3px solid #c52a1f07;
+                background-color: #c52a1f3a;
+            }
+
+            .btn-primary {
+                color: white;
+                background-color: #c52a1f;
+                border: none;
+                font-family: 'Oswald', sans-serif;
+                font-size: 2em;
+                padding: 0.5em 1em;
+                margin: 0.5em;
+                border-radius: 5px;
+                cursor: pointer;
+                -webkit-transition-duration: 0.3s;
+                transition-duration: 0.3s;
+            }
+
+            .btn-primary:hover {
+                background-color: #a0231a;
+                -webkit-transform: translateY(-7px);
+                        transform: translateY(-7px);
+                -webkit-box-shadow: 0 6px 30px -10px #e40a0a7e;
+                        box-shadow: 0 6px 30px -10px #e40a0a7e;
+            }
+
+            .btn-primary:link, .btn-primary:visited, .btn-primary:hover, .btn-primary:active {
+                color: white;
+                text-decoration: none;
+            }
+        </style>
+    </head>
+    <body>
+        <div class="container">
+            <div class="cover">
+                <a class="btn-primary" href="/greenstone3/library/">Looking for Greenstone instead?</a>
+            </div>
+        </div>
+    </body>
+</html>
