| /* |
| * 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.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; |
| |
| 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; |
| |
| /** |
| * 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 { |
| URL.setURLStreamHandlerFactory(new MemoryURLStreamHandlerFactory()); |
| |
| ForkServer server = new ForkServer(System.in, System.out); |
| 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) |
| throws IOException { |
| this.input = |
| new DataInputStream(new CheckedInputStream(input, this)); |
| this.output = |
| new DataOutputStream(new CheckedOutputStream(output, this)); |
| } |
| |
| public void run() { |
| try { |
| while (active) { |
| active = false; |
| Thread.sleep(5000); |
| } |
| System.exit(0); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| public void processRequests() { |
| try { |
| 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); |
| ForkObjectInputStream.sendObject(e.getCause(), 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; |
| } |
| |
| //-------------------------------------------------------------< Checsum > |
| |
| 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() { |
| } |
| |
| } |