You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tika.apache.org by ta...@apache.org on 2020/07/17 17:06:38 UTC

[tika] branch main updated: TIKA-3129 -- add a status endpoint to report server status. Users must turn it on via the commandline -status option.

This is an automated email from the ASF dual-hosted git repository.

tallison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 23329a6  TIKA-3129 -- add a status endpoint to report server status.  Users must turn it on via the commandline -status option.
23329a6 is described below

commit 23329a6bfb41098022554f751882e86168a965ce
Author: tallison <ta...@apache.org>
AuthorDate: Fri Jul 17 13:01:43 2020 -0400

    TIKA-3129 -- add a status endpoint to report server status.  Users must turn it on
    via the commandline -status option.
---
 .../java/org/apache/tika/server/ServerStatus.java  |  9 ++-
 .../java/org/apache/tika/server/TikaServerCli.java |  9 +++
 .../tika/server/resource/TikaServerStatus.java     | 44 +++++++++++++++
 .../apache/tika/server/writer/JSONObjWriter.java   | 64 ++++++++++++++++++++++
 .../apache/tika/server/TikaServerStatusTest.java   | 56 +++++++++++++++++++
 5 files changed, 181 insertions(+), 1 deletion(-)

diff --git a/tika-server/src/main/java/org/apache/tika/server/ServerStatus.java b/tika-server/src/main/java/org/apache/tika/server/ServerStatus.java
index 255ce70..32d74cf 100644
--- a/tika-server/src/main/java/org/apache/tika/server/ServerStatus.java
+++ b/tika-server/src/main/java/org/apache/tika/server/ServerStatus.java
@@ -81,6 +81,8 @@ public class ServerStatus {
     private final boolean isLegacy;
     private STATUS status = STATUS.OPERATING;
 
+    private volatile long lastStarted = Instant.now().toEpochMilli();
+
     public ServerStatus() {
         isLegacy = false;
     }
@@ -91,7 +93,9 @@ public class ServerStatus {
 
     public synchronized long start(TASK task, String fileName) {
         long taskId = counter.incrementAndGet();
-        tasks.put(taskId, new TaskStatus(task, Instant.now(), fileName));
+        Instant now = Instant.now();
+        lastStarted = now.toEpochMilli();
+        tasks.put(taskId, new TaskStatus(task, now, fileName));
         return taskId;
     }
 
@@ -126,6 +130,9 @@ public class ServerStatus {
         return counter.get();
     }
 
+    public long getMillisSinceLastParseStarted() {
+        return Instant.now().toEpochMilli()-lastStarted;
+    }
     /**
      *
      * @return true if this is legacy, otherwise whether or not status == OPERATING.
diff --git a/tika-server/src/main/java/org/apache/tika/server/TikaServerCli.java b/tika-server/src/main/java/org/apache/tika/server/TikaServerCli.java
index 10616cd..d1b6baf 100644
--- a/tika-server/src/main/java/org/apache/tika/server/TikaServerCli.java
+++ b/tika-server/src/main/java/org/apache/tika/server/TikaServerCli.java
@@ -53,12 +53,14 @@ import org.apache.tika.server.resource.TikaDetectors;
 import org.apache.tika.server.resource.TikaMimeTypes;
 import org.apache.tika.server.resource.TikaParsers;
 import org.apache.tika.server.resource.TikaResource;
+import org.apache.tika.server.resource.TikaServerStatus;
 import org.apache.tika.server.resource.TikaVersion;
 import org.apache.tika.server.resource.TikaWelcome;
 import org.apache.tika.server.resource.TranslateResource;
 import org.apache.tika.server.resource.UnpackerResource;
 import org.apache.tika.server.writer.CSVMessageBodyWriter;
 import org.apache.tika.server.writer.JSONMessageBodyWriter;
+import org.apache.tika.server.writer.JSONObjWriter;
 import org.apache.tika.server.writer.MetadataListMessageBodyWriter;
 import org.apache.tika.server.writer.TarWriter;
 import org.apache.tika.server.writer.TextMessageBodyWriter;
@@ -102,6 +104,7 @@ public class TikaServerCli {
         options.addOption("dml", "digestMarkLimit", true, "max number of bytes to mark on stream for digest");
         options.addOption("l", "log", true, "request URI log level ('debug' or 'info')");
         options.addOption("s", "includeStack", false, "whether or not to return a stack trace\nif there is an exception during 'parse'");
+        options.addOption("status", false, "enable the status endpoint");
         options.addOption("?", "help", false, "this help message");
         options.addOption("enableUnsecureFeatures", false, "this is required to enable fileUrl.");
         options.addOption("enableFileUrl", false, "allows user to pass in fileUrl instead of InputStream.");
@@ -305,6 +308,9 @@ public class TikaServerCli {
             rCoreProviders.add(new SingletonResourceProvider(new TikaDetectors()));
             rCoreProviders.add(new SingletonResourceProvider(new TikaParsers()));
             rCoreProviders.add(new SingletonResourceProvider(new TikaVersion()));
+            if (line.hasOption("status")) {
+                rCoreProviders.add(new SingletonResourceProvider(new TikaServerStatus(serverStatus)));
+            }
             List<ResourceProvider> rAllProviders = new ArrayList<>(rCoreProviders);
             rAllProviders.add(new SingletonResourceProvider(new TikaWelcome(rCoreProviders)));
             sf.setResourceProviders(rAllProviders);
@@ -318,6 +324,9 @@ public class TikaServerCli {
             providers.add(new XMPMessageBodyWriter());
             providers.add(new TextMessageBodyWriter());
             providers.add(new TikaServerParseExceptionMapper(returnStackTrace));
+            if (line.hasOption("status")) {
+                providers.add(new JSONObjWriter());
+            }
             if (logFilter != null) {
                 providers.add(logFilter);
             }
diff --git a/tika-server/src/main/java/org/apache/tika/server/resource/TikaServerStatus.java b/tika-server/src/main/java/org/apache/tika/server/resource/TikaServerStatus.java
new file mode 100644
index 0000000..2e55221
--- /dev/null
+++ b/tika-server/src/main/java/org/apache/tika/server/resource/TikaServerStatus.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.tika.server.resource;
+
+import org.apache.tika.server.ServerStatus;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Path("/status")
+public class TikaServerStatus {
+    private final ServerStatus serverStatus;
+
+    public TikaServerStatus(ServerStatus serverStatus) {
+        this.serverStatus = serverStatus;
+    }
+
+    @GET
+    @Produces("application/json")
+    public Map<String, Object> getStatus() {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("status", serverStatus.getStatus());
+        map.put("millis_since_last_parse_started", serverStatus.getMillisSinceLastParseStarted());
+        map.put("files_processed", serverStatus.getFilesProcessed());
+        return map;
+    }
+}
diff --git a/tika-server/src/main/java/org/apache/tika/server/writer/JSONObjWriter.java b/tika-server/src/main/java/org/apache/tika/server/writer/JSONObjWriter.java
new file mode 100644
index 0000000..08851d6
--- /dev/null
+++ b/tika-server/src/main/java/org/apache/tika/server/writer/JSONObjWriter.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.tika.server.writer;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.serialization.JsonMetadata;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@Provider
+@Produces(MediaType.APPLICATION_JSON)
+public class JSONObjWriter implements MessageBodyWriter<Map<String, Object>> {
+    private static Gson GSON = new GsonBuilder().setPrettyPrinting().create();
+
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return Map.class.isAssignableFrom(type);
+    }
+
+    public long getSize(Metadata data, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    @Override
+    public void writeTo(Map<String, Object> map, Class<?> type, Type genericType, Annotation[] annotations,
+                        MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
+            throws IOException, WebApplicationException {
+        Writer writer = new OutputStreamWriter(entityStream, UTF_8);
+        GSON.toJson(map, writer);
+        writer.flush();
+        entityStream.flush();
+    }
+}
diff --git a/tika-server/src/test/java/org/apache/tika/server/TikaServerStatusTest.java b/tika-server/src/test/java/org/apache/tika/server/TikaServerStatusTest.java
new file mode 100644
index 0000000..28e62e7
--- /dev/null
+++ b/tika-server/src/test/java/org/apache/tika/server/TikaServerStatusTest.java
@@ -0,0 +1,56 @@
+package org.apache.tika.server;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.tika.server.resource.RecursiveMetadataResource;
+import org.apache.tika.server.resource.TikaResource;
+import org.apache.tika.server.resource.TikaServerStatus;
+import org.apache.tika.server.writer.JSONMessageBodyWriter;
+import org.apache.tika.server.writer.JSONObjWriter;
+import org.apache.tika.server.writer.MetadataListMessageBodyWriter;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TikaServerStatusTest extends CXFTestBase {
+
+    private final static String STATUS_PATH = "/status";
+
+    @Override
+    protected void setUpResources(JAXRSServerFactoryBean sf) {
+        sf.setResourceClasses(TikaServerStatus.class);
+        sf.setResourceProvider(TikaServerStatus.class,
+                new SingletonResourceProvider(new TikaServerStatus(new ServerStatus())));
+    }
+
+    @Override
+    protected void setUpProviders(JAXRSServerFactoryBean sf) {
+        List<Object> providers = new ArrayList<>();
+        providers.add(new JSONObjWriter());
+        sf.setProviders(providers);
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        Response response = WebClient.create(endPoint + STATUS_PATH).get();
+        String jsonString =
+                getStringFromInputStream((InputStream) response.getEntity());
+        JsonObject root = JsonParser.parseString(jsonString).getAsJsonObject();
+        assertTrue(root.has("status"));
+        assertTrue(root.has("millis_since_last_parse_started"));
+        assertTrue(root.has("files_processed"));
+        assertEquals("OPERATING", root.getAsJsonPrimitive("status").getAsString());
+        assertEquals(0, root.getAsJsonPrimitive("files_processed").getAsInt());
+        long millis = root.getAsJsonPrimitive("millis_since_last_parse_started").getAsInt();
+        assertTrue(millis > 0 && millis < 360000);
+    }
+}