/**
* Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2006-2016
*
* 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 org.italiangrid.voms.container;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.DispatcherType;
import org.apache.commons.io.FileUtils;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppProvider;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.webapp.WebAppContext;
import org.italiangrid.voms.container.lifecycle.VOListener;
import org.italiangrid.voms.container.lifecycle.VOMSESListener;
import org.italiangrid.voms.status.VOMSStatusFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VOMSAppProvider extends AbstractLifeCycle implements AppProvider {
public static final String VOMSES_APP_KEY = "__vomses__";
public static final String DEFAULT_TMP_PREFIX = "/var/tmp";
public static final int DEFAULT_SCAN_INTERVAL_IN_SECONDS = 10;
public static final String ORACLE_JAR_NAME = "ojdbc6.jar";
private static final Logger log = LoggerFactory
.getLogger(VOMSAppProvider.class);
private String configurationDir;
private String warFile;
private String deploymentDir;
private String workDir;
private String hostname;
private String port;
private int scanIntervalInSeconds = DEFAULT_SCAN_INTERVAL_IN_SECONDS;
private Scanner scanner;
private final Scanner.DiscreteListener scannerListener = new Scanner.DiscreteListener() {
private String getBasename(String filename) {
File f = new File(filename);
return f.getName();
}
@Override
public void fileRemoved(String filename) throws Exception {
VOMSAppProvider.this.stopVO(getBasename(filename));
}
@Override
public void fileChanged(String filename) throws Exception {
// Do nothing
}
@Override
public void fileAdded(String filename) throws Exception {
VOMSAppProvider.this.startVO(getBasename(filename));
}
};
private DeploymentManager deploymentManager;
private Map<String, App> vomsApps = new HashMap<String, App>();
public VOMSAppProvider() {
}
protected void confDirSanityChecks() {
File confDirFile = new File(configurationDir);
if (!confDirFile.exists())
throw new IllegalArgumentException("Configuration dir does not exist: "
+ configurationDir);
if (!confDirFile.isDirectory())
throw new IllegalArgumentException("Configuration dir is not "
+ "a directory: " + configurationDir);
}
protected List<String> getConfiguredVONames() {
confDirSanityChecks();
List<String> voNames = new ArrayList<String>();
File confDirFile = new File(configurationDir);
File[] voFiles = confDirFile.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
for (File f : voFiles) {
voNames.add(f.getName());
}
return voNames;
}
protected App createApp(String voName) {
return new App(deploymentManager, this, voName);
}
private boolean voNameIsValid(String voName) {
String voConfDir = String.format("%s/%s", configurationDir, voName)
.replaceAll("/+", "/");
File f = new File(voConfDir);
return (f.exists() && f.isDirectory());
}
public void startVOMSES() {
log.debug("Request to start VOMSES webapp");
App a = createApp(VOMSES_APP_KEY);
if (a != null) {
vomsApps.put(VOMSES_APP_KEY, a);
deploymentManager.addApp(a);
}
}
public void startVO(String voName) {
log.info("Starting vo {}", voName);
if (!voNameIsValid(voName)) {
log.error("VO {} is not configured on this host!", voName);
return;
}
App a = createApp(voName);
if (a != null) {
vomsApps.put(voName, a);
deploymentManager.addApp(a);
}
}
public void stopVO(String voName) {
log.info("Stopping vo {}", voName);
if (!voNameIsValid(voName)) {
log.error("VO {} is not configured on this host!");
return;
}
App a = vomsApps.remove(voName);
if (a != null)
deploymentManager.removeApp(a);
}
@Override
public void setDeploymentManager(DeploymentManager deploymentManager) {
this.deploymentManager = deploymentManager;
}
/**
* Initializes the Jetty temp directory as the default directory created by
* Jetty confuses xwork which has a bug and doesn't find classes when the WAR
* is expanded in the tmp directory.
*
* TODO: check if recent versions of xwork solve this.
*/
protected File getJettyTmpDirForVO(String vo) {
String tmpWorkDir = (workDir == null) ? DEFAULT_TMP_PREFIX : workDir;
String baseDirPath = String.format("%s/%s/%s", tmpWorkDir, "voms-webapp",
vo).replaceAll("/+", "/");
File basePath = new File(baseDirPath);
if (basePath.exists()) {
try {
log.debug("Cleaning up VO tmp dir: {}", basePath);
FileUtils.deleteDirectory(basePath);
} catch (IOException e) {
log.error("Error removing temp directory {}: {}",
basePath.getAbsolutePath(), e.getMessage());
throw new RuntimeException(e);
}
}
log.debug("Creating VO tmp dir: {}", basePath);
basePath.mkdirs();
return basePath;
}
protected ContextHandler configureWebApp(String vo) {
String contextPath = String.format("/voms/%s", vo);
WebAppContext vomsWebappContext = new WebAppContext();
vomsWebappContext.setContextPath(contextPath);
vomsWebappContext.setTempDirectory(getJettyTmpDirForVO(vo));
vomsWebappContext.setCompactPath(true);
vomsWebappContext.setParentLoaderPriority(false);
File webArchive = new File(warFile);
if (webArchive.isDirectory()) {
String webXMLPath = String.format("%s/WEB-INF/web.xml",
webArchive.getAbsolutePath());
vomsWebappContext.setDescriptor(webXMLPath);
vomsWebappContext.setResourceBase(webArchive.getAbsolutePath());
} else {
vomsWebappContext.setWar(warFile);
}
// Consider logback and slf4j server classes
vomsWebappContext.addServerClass("ch.qos.logback.");
vomsWebappContext.addServerClass("org.slf4j.");
// Oracle driver
vomsWebappContext.addSystemClass("oracle.");
// Guava
vomsWebappContext.addSystemClass("com.google.");
vomsWebappContext.setInitParameter("VO_NAME", vo);
vomsWebappContext.setInitParameter("CONF_DIR", configurationDir);
vomsWebappContext.setInitParameter("HOST", hostname);
vomsWebappContext.setInitParameter("PORT", port);
vomsWebappContext.setConnectorNames(new String[] {
Container.HTTPS_CONNECTOR_NAME, "voms-" + vo });
vomsWebappContext.addLifeCycleListener(VOListener.INSTANCE);
return vomsWebappContext;
}
protected ContextHandler configureVOMSES() {
String webappResourceDir = this.getClass().getClassLoader()
.getResource("status-webapp").toExternalForm();
WebAppContext statusContext = new WebAppContext();
statusContext.setContextPath("/");
statusContext.setResourceBase(webappResourceDir);
statusContext.setCompactPath(true);
statusContext.setParentLoaderPriority(true);
statusContext.setInitParameter("host", hostname);
statusContext.setInitParameter("confdir", configurationDir);
statusContext.setConnectorNames(new String[] {
Container.HTTP_CONNECTOR_NAME, Container.HTTPS_CONNECTOR_NAME });
VOMSStatusFilter f = new VOMSStatusFilter(deploymentManager, hostname, port);
FilterHolder fh = new FilterHolder(f);
statusContext.addFilter(fh, "/*",
EnumSet.of(DispatcherType.FORWARD, DispatcherType.REQUEST));
statusContext.setThrowUnavailableOnStartupException(true);
statusContext.addLifeCycleListener(new VOMSESListener());
return statusContext;
}
@Override
public ContextHandler createContextHandler(App app) throws Exception {
ContextHandler webApp = null;
if (app != null) {
if (app.getOriginId().equals(VOMSES_APP_KEY)) {
webApp = configureVOMSES();
} else {
webApp = configureWebApp(app.getOriginId());
}
}
return webApp;
}
public Map<String, App> getDeployedApps() {
return vomsApps;
}
@Override
protected void doStart() throws Exception {
log.debug("Starting VOMS App provider.");
File scanDir = new File(deploymentDir);
if (!scanDir.exists() || !scanDir.isDirectory()) {
throw new IllegalArgumentException("VOMS Admin deployment dir "
+ "does not exist or is not a directory: " + scanDir.getAbsolutePath());
}
scanner = new Scanner();
scanner.setScanDirs(Collections.singletonList(scanDir));
scanner.setScanInterval(scanIntervalInSeconds);
scanner.setRecursive(false);
scanner.setReportDirs(false);
scanner.addListener(scannerListener);
startVOMSES();
scanner.start();
}
@Override
protected void doStop() throws Exception {
log.debug("Stopping VOMS App provider.");
if (scanner != null) {
scanner.stop();
scanner.removeListener(scannerListener);
scanner = null;
}
}
/**
* @return the configurationDir
*/
public String getConfigurationDir() {
return configurationDir;
}
/**
* @param configurationDir
* the configurationDir to set
*/
public void setConfigurationDir(String configurationDir) {
this.configurationDir = configurationDir;
}
/**
* @return the warFile
*/
public String getWarFile() {
return warFile;
}
/**
* @param warFile
* the warFile to set
*/
public void setWarFile(String warFile) {
this.warFile = warFile;
}
/**
* @return the deploymentDir
*/
public String getDeploymentDir() {
return deploymentDir;
}
/**
* @param deploymentDir
* the deploymentDir to set
*/
public void setDeploymentDir(String deploymentDir) {
this.deploymentDir = deploymentDir;
}
/**
* @return the hostname
*/
public String getHostname() {
return hostname;
}
/**
* @param hostname
* the hostname to set
*/
public void setHostname(String hostname) {
this.hostname = hostname;
}
/**
* @return the port
*/
public String getPort() {
return port;
}
/**
* @param port
* the port to set
*/
public void setPort(String port) {
this.port = port;
}
public String getWorkDir() {
return workDir;
}
public void setWorkDir(String workDir) {
this.workDir = workDir;
}
}