/*
* Copyright 2009-2011 Collaborative Research Centre SFB 632
*
* 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 annis.service.internal;
import annis.AnnisBaseRunner;
import annis.AnnisRunnerException;
import annis.AnnisXmlContextHelper;
import annis.dao.QueryDao;
import annis.exceptions.AnnisException;
import annis.security.MultipleIniWebEnvironment;
import annis.service.objects.AnnisCorpus;
import annis.utils.Utils;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
import com.sun.jersey.spi.container.WebApplication;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import com.sun.jersey.spi.spring.container.SpringComponentProviderFactory;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.apache.shiro.web.env.EnvironmentLoader;
import org.apache.shiro.web.env.EnvironmentLoaderListener;
import org.apache.shiro.web.servlet.ShiroFilter;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.GzipFilter;
import org.eclipse.jetty.util.thread.ExecutorThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.GenericXmlApplicationContext;
import java.util.List;
import java.util.LinkedList;
public class AnnisServiceRunner extends AnnisBaseRunner
{
private static final Logger log = LoggerFactory.getLogger(
AnnisServiceRunner.class);
private static AnnisServiceRunner annisServiceRunner;
private boolean isShutdownRequested = false;
private int errorCode = 0;
private static Thread mainThread;
private Server server;
private boolean useAuthentification = true;
private Integer overridePort = null;
private GenericXmlApplicationContext ctx;
public AnnisServiceRunner()
{
this(null);
}
public AnnisServiceRunner(Integer port)
{
this.overridePort = port;
boolean nosecurity = Boolean.parseBoolean(System.getProperty(
"annis.nosecurity", "false"));
this.useAuthentification = !nosecurity;
}
public static void main(String[] args) throws Exception
{
boolean daemonMode = false;
if (args.length == 1 && ("-d".equals(args[0])))
{
daemonMode = true;
}
AnnisBaseRunner.setupLogging(!daemonMode);
mainThread = Thread.currentThread();
annisServiceRunner = new AnnisServiceRunner();
// run as a deamon?
if (daemonMode)
{
log.info("Running in Daemon mode");
File pidFile = new File(System.getProperty("annisservice.pid_file"));
pidFile.deleteOnExit();
annisServiceRunner.start(false);
if (!annisServiceRunner.isShutdownRequested)
{
closeSystemStreams();
}
}
// no, run in debug mode
else
{
log.info("Running in Debug mode");
annisServiceRunner.start(false);
}
if (!annisServiceRunner.isShutdownRequested)
{
addShutdownHook();
}
try
{
while (!annisServiceRunner.isShutdownRequested)
{
Thread.sleep(1000);
}
}
catch (InterruptedException ex)
{
log.error("interrupted in endless loop", ex);
}
// explicitly exit so we can decide if there was an error or not and everything is closed.
if(annisServiceRunner.errorCode != 0)
{
System.exit(annisServiceRunner.errorCode);
}
}
/**
* shutdown the AnnisService - ensure that current work load finishes
*/
public void shutdown()
{
log.info("Shutting down...");
isShutdownRequested = true;
try
{
mainThread.join();
}
catch (InterruptedException e)
{
log.error(
"Interrupted which waiting on main daemon thread to complete.");
}
}
private static void closeSystemStreams()
{
System.err.close();
System.out.close();
}
static private void addShutdownHook()
{
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
annisServiceRunner.shutdown();
}
});
}
private void createWebServer()
{
// create beans
ctx = new GenericXmlApplicationContext();
ctx.setValidating(false);
AnnisXmlContextHelper.prepareContext(ctx);
ctx.load("file:" + Utils.getAnnisFile("conf/spring/Service.xml").
getAbsolutePath());
ctx.refresh();
ResourceConfig rc = new PackagesResourceConfig("annis.service.internal",
"annis.provider", "annis.rest.provider");
final IoCComponentProviderFactory factory = new SpringComponentProviderFactory(
rc, ctx);
int port = overridePort == null ? ctx.getBean(QueryServiceImpl.class).getPort() : overridePort;
try
{
// only allow connections from localhost
// if the administrator wants to allow external acccess he *has* to
// use a HTTP proxy which also should use SSL encryption
InetSocketAddress addr = new InetSocketAddress("localhost", port);
server = new Server(addr);
ServletContextHandler context =
new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/");
server.setHandler(context);
server.setThreadPool(new ExecutorThreadPool());
ServletContainer jerseyContainer = new ServletContainer(rc)
{
@Override
protected void initiate(ResourceConfig rc, WebApplication wa)
{
wa.initiate(rc, factory);
}
};
ServletHolder holder = new ServletHolder(jerseyContainer);
context.addServlet(holder, "/*");
context.setInitParameter(EnvironmentLoader.ENVIRONMENT_CLASS_PARAM, MultipleIniWebEnvironment.class.getName());
if (useAuthentification)
{
log.info("Using authentification");
context.setInitParameter(EnvironmentLoader.CONFIG_LOCATIONS_PARAM,
"file:" + System.getProperty("annis.home") + "/conf/shiro.ini,"
+ "file:" + System.getProperty("annis.home") + "/conf/develop_shiro.ini");
}
else
{
log.warn("*NOT* using authentification, your ANNIS service *IS NOT SECURED*");
context.setInitParameter(EnvironmentLoader.CONFIG_LOCATIONS_PARAM,
"file:" + System.getProperty("annis.home") + "/conf/shiro_no_security.ini");
}
EnumSet<DispatcherType> gzipDispatcher = EnumSet.
of(DispatcherType.REQUEST);
context.addFilter(GzipFilter.class, "/*", gzipDispatcher);
// configure Apache Shiro with the web application
context.addEventListener(new EnvironmentLoaderListener());
EnumSet<DispatcherType> shiroDispatchers = EnumSet.of(
DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE,
DispatcherType.ERROR);
context.addFilter(ShiroFilter.class, "/*", shiroDispatchers);
}
catch (IllegalArgumentException ex)
{
log.error("IllegalArgumentException at ANNIS service startup", ex);
isShutdownRequested = true;
errorCode = 101;
}
catch (NullPointerException ex)
{
log.error("NullPointerException at ANNIS service startup", ex);
isShutdownRequested = true;
errorCode = 101;
}
catch(AnnisRunnerException ex)
{
errorCode = ex.getExitCode();
}
}
/**
* Creates and starts the server
*
* @param rethrowExceptions Set to true if you want to get exceptions
* re-thrown to parent
*/
public void start(boolean rethrowExceptions) throws Exception
{
log.info("Starting up REST...");
try
{
createWebServer();
if (server == null)
{
isShutdownRequested = true;
errorCode = 100;
}
else
{
server.start();
}
}
catch (Exception ex)
{
log.error("could not start ANNIS REST service", ex);
isShutdownRequested = true;
errorCode = 100;
if (rethrowExceptions)
{
if (!(ex instanceof AnnisException) && ex.getCause() instanceof AnnisException)
{
throw ((AnnisException) ex.getCause());
}
else
{
throw (ex);
}
}
}
}
/**
* True if authorization is enabled.
*
* @return
*/
public boolean isUseAuthentification()
{
return useAuthentification;
}
/**
* Set wether you want to protect the service using authentification.
*
* Default value is true.
*
* @param useAuthentification True if service should be authentificated, false
* if not.
*/
public void setUseAuthentification(boolean useAuthentification)
{
this.useAuthentification = useAuthentification;
}
/**
* Set the timeout in milliseconds
* @param milliseconds Timeout if greater than zero, disabled timeout if less then zero.
*/
public void setTimeout(int milliseconds)
{
if(ctx != null)
{
QueryDao dao = (QueryDao) ctx.getBean("queryDao");
if(dao != null)
{
dao.setTimeout(milliseconds);
}
}
}
public int getTimeout()
{
if(ctx != null)
{
QueryDao dao = (QueryDao) ctx.getBean("queryDao");
if(dao != null)
{
return dao.getTimeout();
}
}
return -1;
}
public List<AnnisCorpus> getCorpora()
{
if(ctx != null)
{
QueryDao dao = (QueryDao) ctx.getBean("queryDao");
if(dao != null)
{
return dao.listCorpora();
}
}
return new LinkedList<>();
}
}