blob: 6aa940627fe96a03922c414d5f082505ce152687 [file] [log] [blame]
// 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 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 jsvc.
*
* <p>Example execution with jsvc:
* <pre>jsvc -pidfile adaptor.pid -cp someadaptor-withlib.jar \
* com.google.enterprise.adaptor.Daemon package.SomeAdaptor</pre>
*/
public class Daemon implements org.apache.commons.daemon.Daemon {
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);
}
@VisibleForTesting
Application getApplication() {
return app;
}
}