/* * 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.fork; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.NotSerializableException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.zip.CheckedInputStream; import java.util.zip.CheckedOutputStream; import java.util.zip.Checksum; import org.apache.tika.exception.TikaException; class ForkServer implements Runnable, Checksum { public static final byte ERROR = -1; public static final byte DONE = 0; public static final byte CALL = 1; public static final byte PING = 2; public static final byte RESOURCE = 3; public static final byte READY = 4; //milliseconds to sleep before checking to see if there has been any reading/writing //If no reading or writing in this time, shutdown the server. private long serverPulseMillis = 5000; /** * Starts a forked server process using the standard input and output * streams for communication with the parent process. Any attempts by * stray code to read from standard input or write to standard output * is redirected to avoid interfering with the communication channel. * * @param args command line arguments, ignored * @throws Exception if the server could not be started */ public static void main(String[] args) throws Exception { long serverPulseMillis = -1; if (args.length > 0) { serverPulseMillis = Long.parseLong(args[0]); } URL.setURLStreamHandlerFactory(new MemoryURLStreamHandlerFactory()); ForkServer server = new ForkServer(System.in, System.out, serverPulseMillis); System.setIn(new ByteArrayInputStream(new byte[0])); System.setOut(System.err); Thread watchdog = new Thread(server, "Tika Watchdog"); watchdog.setDaemon(true); watchdog.start(); server.processRequests(); } /** Input stream for reading from the parent process */ private final DataInputStream input; /** Output stream for writing to the parent process */ private final DataOutputStream output; private volatile boolean active = true; /** * Sets up a forked server instance using the given stdin/out * communication channel. * * @param input input stream for reading from the parent process * @param output output stream for writing to the parent process * @throws IOException if the server instance could not be created */ public ForkServer(InputStream input, OutputStream output, long serverPulseMillis) throws IOException { this.input = new DataInputStream(new CheckedInputStream(input, this)); this.output = new DataOutputStream(new CheckedOutputStream(output, this)); this.serverPulseMillis = serverPulseMillis; } public void run() { try { while (active) { active = false; Thread.sleep(serverPulseMillis); } System.exit(0); } catch (InterruptedException e) { } } public void processRequests() { try { output.writeByte(READY); output.flush(); ClassLoader loader = (ClassLoader) readObject( ForkServer.class.getClassLoader()); Thread.currentThread().setContextClassLoader(loader); Object object = readObject(loader); while (true) { int request = input.read(); if (request == -1) { break; } else if (request == PING) { output.writeByte(PING); } else if (request == CALL) { call(loader, object); } else { throw new IllegalStateException("Unexpected request"); } output.flush(); } } catch (Throwable t) { t.printStackTrace(); } System.err.flush(); } private void call(ClassLoader loader, Object object) throws Exception { Method method = getMethod(object, input.readUTF()); Object[] args = new Object[method.getParameterTypes().length]; for (int i = 0; i < args.length; i++) { args[i] = readObject(loader); } try { method.invoke(object, args); output.write(DONE); } catch (InvocationTargetException e) { output.write(ERROR); // Try to send the underlying Exception itself Throwable toSend = e.getCause(); try { ForkObjectInputStream.sendObject(toSend, output); } catch (NotSerializableException nse) { // Need to build a serializable version of it TikaException te = new TikaException( toSend.getMessage() ); te.setStackTrace( toSend.getStackTrace() ); ForkObjectInputStream.sendObject(te, output); } } } private Method getMethod(Object object, String name) { Class<?> klass = object.getClass(); while (klass != null) { for (Class<?> iface : klass.getInterfaces()) { for (Method method : iface.getMethods()) { if (name.equals(method.getName())) { return method; } } } klass = klass.getSuperclass(); } return null; } /** * Deserializes an object from the given stream. The serialized object * is expected to be preceded by a size integer, that is used for reading * the entire serialization into a memory before deserializing it. * * @param input input stream from which the serialized object is read * @param loader class loader to be used for loading referenced classes * @throws IOException if the object could not be deserialized * @throws ClassNotFoundException if a referenced class is not found */ private Object readObject(ClassLoader loader) throws IOException, ClassNotFoundException { Object object = ForkObjectInputStream.readObject(input, loader); if (object instanceof ForkProxy) { ((ForkProxy) object).init(input, output); } // Tell the parent process that we successfully received this object output.writeByte(ForkServer.DONE); output.flush(); return object; } //------------------------------------------------------------< Checksum > public void update(int b) { active = true; } public void update(byte[] b, int off, int len) { active = true; } public long getValue() { return 0; } public void reset() { } }