/** * 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; } }