This is an automated email from the ASF dual-hosted git repository. tallison pushed a commit to branch branch_1x in repository https://gitbox.apache.org/repos/asf/tika.git
commit 9f441f51667b9d404650034227c758c68712352d Author: tallison <[email protected]> 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); + } +}
