// 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;
    final CountDownLatch latch = new CountDownLatch(1);
    // 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.
    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);
        } finally {
          latch.countDown();
        }
      }
    }).start();
    latch.await(5, TimeUnit.SECONDS);
  }

  @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();
    }
  }
}

@@ -3078,64 +3078,8 @@
 xt;%0A
-    final CountDownLatch latch = new CountDownLatch(1);%0A
     
@@ -3587,16 +3587,32 @@
 ick.%0A   
+ Thread thread =
  new Thr
@@ -3946,50 +3946,9 @@
    %7D
- finally %7B%0A          latch.countDown();%0A  
+%0A
     
@@ -3959,18 +3959,22 @@
     
-  %7D
+%7D);
 %0A    
-%7D)
+thread
 .sta
@@ -3987,39 +3987,28 @@
     
-latch.await(5, TimeUnit.SECONDS
+thread.join(5 * 1000
 );%0A 

Remove unhelpful CountDownLatch from startup