| // Copyright 2013 Google Inc. All Rights Reserved. |
| // |
| // 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. |
| |
| package com.google.enterprise.adaptor; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| |
| import org.apache.commons.daemon.DaemonContext; |
| import org.apache.commons.daemon.DaemonController; |
| |
| import java.util.Arrays; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Allows running an adaptor as a daemon when used in conjunction with procrun |
| * or {@code jsvc}. |
| * |
| * <p>Example execution with {@code jsvc}: |
| * <pre>jsvc -pidfile adaptor.pid -cp someadaptor-withlib.jar \ |
| * com.google.enterprise.adaptor.Daemon package.SomeAdaptor</pre> |
| * |
| * <p>Example registration with {@code prunsrv}, the procrun service |
| * application: |
| * <pre>prunsrv install someadaptor --StartPath="%CD%" ^ |
| * --Classpath=someadaptor-withlib.jar ^ |
| * --StartMode=jvm --StartClass=com.google.enterprise.adaptor.Daemon ^ |
| * --StartMethod=serviceStart --StartParams=package.SomeAdaptor |
| * --StopMode=jvm --StopClass=com.google.enterprise.adaptor.Daemon ^ |
| * --StopMethod=serviceStop</pre> |
| * |
| * <p>Where {@code someadaptor} is a unique, arbitrary service name. |
| * |
| * <p>Typical setups will also want to provide extra arguments with {@code |
| * procrun}: |
| * <pre>prunsrv ... ^ |
| * --StdOutput=stdout.log --StdError=stderr.log ^ |
| * ++JvmOptions=-Djava.util.logging.config.file=logging.properties</pre> |
| |
| */ |
| public class Daemon implements org.apache.commons.daemon.Daemon { |
| /** Windows-specific instance for keeping track of running Daemon. */ |
| private static Daemon windowsDaemon; |
| |
| private Application app; |
| private DaemonContext context; |
| |
| @Override |
| public synchronized void init(DaemonContext context) throws Exception { |
| if (this.context != null) { |
| throw new IllegalStateException("Already initialized"); |
| } |
| this.context = context; |
| String[] args = context.getArguments(); |
| if (args.length < 1) { |
| throw new IllegalArgumentException( |
| "Missing argument: adaptor class name"); |
| } |
| Adaptor adaptor |
| = Class.forName(args[0]).asSubclass(Adaptor.class).newInstance(); |
| args = Arrays.copyOfRange(args, 1, args.length); |
| |
| app = Application.daemonMain(adaptor, args); |
| app.daemonInit(); |
| } |
| |
| @Override |
| public synchronized void destroy() { |
| if (app != null) { |
| app.daemonDestroy(5, TimeUnit.SECONDS); |
| } |
| context = null; |
| app = null; |
| } |
| |
| @Override |
| public void start() throws Exception { |
| final Application savedApp; |
| final DaemonContext savedContext; |
| // Save values so that there aren't any races with stop/destroy. |
| synchronized (this) { |
| savedApp = this.app; |
| savedContext = this.context; |
| } |
| // Run in a new thread so that stop() can be called before we complete |
| // starting (since starting can take a long time if the Adaptor keeps |
| // throwing an exception). However, we still try to wait for start to |
| // complete normally to ease testing and improve the user experience in the |
| // common case of starting being quick. |
| Thread thread = new Thread(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| savedApp.daemonStart(); |
| } catch (InterruptedException ex) { |
| // We must be shutting down. |
| Thread.currentThread().interrupt(); |
| } catch (Exception ex) { |
| savedContext.getController().fail(ex); |
| } |
| } |
| }); |
| thread.start(); |
| thread.join(5 * 1000); |
| } |
| |
| @Override |
| public synchronized void stop() throws Exception { |
| app.daemonStop(5, TimeUnit.SECONDS); |
| } |
| |
| public static synchronized void serviceStart(String[] args) throws Exception { |
| if (windowsDaemon != null) { |
| throw new IllegalStateException("Service already running"); |
| } |
| windowsDaemon = new Daemon(); |
| windowsDaemon.init(new WindowsDaemonContext(args)); |
| windowsDaemon.start(); |
| } |
| |
| public static synchronized void serviceStop(String[] args) throws Exception { |
| windowsDaemon.stop(); |
| windowsDaemon.destroy(); |
| windowsDaemon = null; |
| } |
| |
| @VisibleForTesting |
| Application getApplication() { |
| return app; |
| } |
| |
| private static class WindowsDaemonContext implements DaemonContext { |
| private final String[] args; |
| |
| public WindowsDaemonContext(String[] args) { |
| this.args = Arrays.copyOf(args, args.length); |
| } |
| |
| /** |
| * Returns arguments, similar to the String[] provided to main(). The |
| * returned array is not safe for modification, which is the same behavior |
| * as Apache Daemon's context used on Unix environments. |
| */ |
| @Override |
| public String[] getArguments() { |
| return args; |
| } |
| |
| @Override |
| public DaemonController getController() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| } |