/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.utils;
import java.io.File;
import java.io.FilenameFilter;
import java.net.BindException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.objectweb.proactive.core.config.CentralPAPropertyRepository;
import org.objectweb.proactive.core.util.ProActiveInet;
import org.ow2.proactive.scheduler.core.properties.PASchedulerProperties;
import org.ow2.proactive.web.WebProperties;
public class JettyStarter {
protected static final String FOLDER_TO_DEPLOY = "/dist/war/";
protected static final String HTTP_CONNECTOR_NAME = "http";
protected static final String HTTPS_CONNECTOR_NAME = "https";
private static final Logger logger = Logger.getLogger(JettyStarter.class);
/**
* To run Jetty in standalone mode
*/
public static void main(String[] args) {
BasicConfigurator.configure();
CentralPAPropertyRepository.PA_CLASSLOADING_USEHTTP.setValue(false);
PASchedulerProperties.SCHEDULER_HOME.updateProperty(".");
new JettyStarter().deployWebApplications("", "");
}
public List<String> deployWebApplications(String rmUrl, String schedulerUrl) {
initializeRestProperties();
setSystemPropertyIfNotDefined("rm.url", rmUrl);
setSystemPropertyIfNotDefined("scheduler.url", schedulerUrl);
if (WebProperties.WEB_DEPLOY.getValueAsBoolean()) {
logger.info("Starting the web applications...");
int httpPort = getJettyHttpPort();
int httpsPort = 443;
if (WebProperties.WEB_HTTPS_PORT.isSet()) {
httpsPort = WebProperties.WEB_HTTPS_PORT.getValueAsInt();
}
boolean httpsEnabled = WebProperties.WEB_HTTPS.getValueAsBoolean();
boolean redirectHttpToHttps = WebProperties.WEB_REDIRECT_HTTP_TO_HTTPS.getValueAsBoolean();
int restPort = httpPort;
String httpProtocol;
String[] defaultVirtualHost;
String[] httpVirtualHost = new String[] { "@" + HTTP_CONNECTOR_NAME };
if (httpsEnabled) {
httpProtocol = "https";
defaultVirtualHost = new String[] { "@" + HTTPS_CONNECTOR_NAME };
restPort = httpsPort;
} else {
defaultVirtualHost = httpVirtualHost;
httpProtocol = "http";
}
Server server = createHttpServer(httpPort, httpsPort, httpsEnabled, redirectHttpToHttps);
server.setStopAtShutdown(true);
HandlerList handlerList = new HandlerList();
if (httpsEnabled && redirectHttpToHttps) {
ContextHandler redirectHandler = new ContextHandler();
redirectHandler.setContextPath("/");
redirectHandler.setHandler(new SecuredRedirectHandler());
redirectHandler.setVirtualHosts(httpVirtualHost);
handlerList.addHandler(redirectHandler);
}
addWarsToHandlerList(handlerList, defaultVirtualHost);
server.setHandler(handlerList);
String schedulerHost = ProActiveInet.getInstance().getHostname();
return startServer(server, schedulerHost, restPort, httpProtocol);
}
return new ArrayList<>();
}
protected int getJettyHttpPort() {
if (WebProperties.WEB_HTTP_PORT.isSet()) {
return WebProperties.WEB_HTTP_PORT.getValueAsInt();
}
return 8080;
}
protected Server createHttpServer(int httpPort, int httpsPort, boolean httpsEnabled, boolean redirectHttpToHttps) {
int maxThreads = 100;
if (WebProperties.WEB_MAX_THREADS.isSet()) {
maxThreads = WebProperties.WEB_MAX_THREADS.getValueAsInt();
}
QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads);
Server server = new Server(threadPool);
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setSendDateHeader(false);
httpConfiguration.setSendServerVersion(false);
Connector[] connectors;
if (httpsEnabled) {
SslContextFactory sslContextFactory = new SslContextFactory();
String httpsKeystore = WebProperties.WEB_HTTPS_KEYSTORE.getValueAsStringOrNull();
String httpsKeystorePassword = WebProperties.WEB_HTTPS_KEYSTORE_PASSWORD.getValueAsStringOrNull();
checkPropertyNotNull(WebProperties.WEB_HTTPS_KEYSTORE.getKey(), httpsKeystore);
checkPropertyNotNull(WebProperties.WEB_HTTPS_KEYSTORE_PASSWORD.getKey(), httpsKeystorePassword);
sslContextFactory.setKeyStorePath(absolutePathOrRelativeToSchedulerHome(httpsKeystore));
sslContextFactory.setKeyStorePassword(httpsKeystorePassword);
if (WebProperties.WEB_HTTPS_TRUSTSTORE.isSet() && WebProperties.WEB_HTTPS_TRUSTSTORE_PASSWORD.isSet()) {
String httpsTrustStore = WebProperties.WEB_HTTPS_TRUSTSTORE.getValueAsString();
String httpsTrustStorePassword = WebProperties.WEB_HTTPS_TRUSTSTORE_PASSWORD.getValueAsString();
sslContextFactory.setTrustStorePath(httpsTrustStore);
sslContextFactory.setTrustStorePassword(httpsTrustStorePassword);
}
HttpConfiguration secureHttpConfiguration = new HttpConfiguration(httpConfiguration);
secureHttpConfiguration.addCustomizer(new SecureRequestCustomizer());
secureHttpConfiguration.setSecurePort(httpsPort);
secureHttpConfiguration.setSecureScheme("https");
secureHttpConfiguration.setSendDateHeader(false);
secureHttpConfiguration.setSendServerVersion(false);
// Connector to listen for HTTPS requests
ServerConnector httpsConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory,
HttpVersion.HTTP_1_1.toString()),
new HttpConnectionFactory(secureHttpConfiguration));
httpsConnector.setName(HTTPS_CONNECTOR_NAME);
httpsConnector.setPort(httpsPort);
if (redirectHttpToHttps) {
// The next two settings allow !403 errors to be redirected to HTTPS
httpConfiguration.setSecureScheme("https");
httpConfiguration.setSecurePort(httpsPort);
// Connector to listen for HTTP requests that are redirected to HTTPS
ServerConnector httpConnector = createHttpConnector(server, httpConfiguration, httpPort);
connectors = new Connector[] { httpConnector, httpsConnector };
} else {
connectors = new Connector[] { httpsConnector };
}
} else {
ServerConnector httpConnector = createHttpConnector(server, httpConfiguration, httpPort);
connectors = new Connector[] { httpConnector };
}
server.setConnectors(connectors);
return server;
}
private void checkPropertyNotNull(String propertyName, String propertyValue) {
if (propertyValue == null) {
logger.error("You need to define property '" + propertyName + "'");
System.exit(-1);
}
}
private ServerConnector createHttpConnector(Server server, HttpConfiguration httpConfiguration, int httpPort) {
ServerConnector httpConnector = new ServerConnector(server);
httpConnector.addConnectionFactory(new HttpConnectionFactory(httpConfiguration));
httpConnector.setName(HTTP_CONNECTOR_NAME);
httpConnector.setPort(httpPort);
return httpConnector;
}
private List<String> startServer(Server server, String schedulerHost, int restPort, String httpProtocol) {
try {
if (server.getHandler() == null) {
logger.info("SCHEDULER_HOME/dist/war folder is empty, nothing is deployed");
} else {
server.start();
if (server.isStarted()) {
return printDeployedApplications(server, schedulerHost, restPort, httpProtocol);
} else {
logger.error("Failed to start web applications");
System.exit(1);
}
}
} catch (BindException bindException) {
logger.error("Failed to start web applications. Port " + restPort + " is already used", bindException);
System.exit(2);
} catch (Exception e) {
logger.error("Failed to start web applications", e);
System.exit(3);
}
return new ArrayList<>();
}
private String getApplicationUrl(String httpProtocol, String schedulerHost, int restPort,
WebAppContext webAppContext) {
return httpProtocol + "://" + schedulerHost + ":" + restPort + webAppContext.getContextPath();
}
private List<String> printDeployedApplications(Server server, String schedulerHost, int restPort,
String httpProtocol) {
HandlerList handlerList = (HandlerList) server.getHandler();
ArrayList<String> applicationsUrls = new ArrayList<>();
if (handlerList.getHandlers() != null) {
for (Handler handler : handlerList.getHandlers()) {
if (!(handler instanceof WebAppContext)) {
continue;
}
WebAppContext webAppContext = (WebAppContext) handler;
Throwable startException = webAppContext.getUnavailableException();
if (startException == null) {
if (!"/".equals(webAppContext.getContextPath())) {
String applicationUrl = getApplicationUrl(httpProtocol, schedulerHost, restPort, webAppContext);
applicationsUrls.add(applicationUrl);
logger.info("The web application " + webAppContext.getContextPath() + " created on " +
applicationUrl);
}
} else {
logger.warn("Failed to start context " + webAppContext.getContextPath(), startException);
}
}
logger.info("*** Get started at " + httpProtocol + "://" + schedulerHost + ":" + restPort + " ***");
}
return applicationsUrls;
}
private void addWarsToHandlerList(HandlerList handlerList, String[] virtualHost) {
File warFolder = new File(getSchedulerHome() + FOLDER_TO_DEPLOY);
File[] warFolderContent = warFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return !"getstarted".equals(name);
}
});
if (warFolderContent != null) {
for (File fileToDeploy : warFolderContent) {
if (isExplodedWebApp(fileToDeploy)) {
addExplodedWebApp(handlerList, fileToDeploy, virtualHost);
} else if (isWarFile(fileToDeploy)) {
addWarFile(handlerList, fileToDeploy, virtualHost);
} else if (isStaticFolder(fileToDeploy)) {
addStaticFolder(handlerList, fileToDeploy, virtualHost);
}
}
}
addGetStartedApplication(handlerList, new File(warFolder, "getstarted"), virtualHost);
}
private void addWarFile(HandlerList handlerList, File file, String[] virtualHost) {
String contextPath = "/" + FilenameUtils.getBaseName(file.getName());
WebAppContext webApp = createWebAppContext(contextPath, virtualHost);
webApp.setWar(file.getAbsolutePath());
handlerList.addHandler(webApp);
logger.debug("Deploying " + contextPath + " using war file " + file);
}
private void addExplodedWebApp(HandlerList handlerList, File file, String[] virtualHost) {
String contextPath = "/" + file.getName();
WebAppContext webApp = createWebAppContext(contextPath, virtualHost);
// Don't scan classes for annotations. Saves 1 second at startup.
webApp.setAttribute("org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern", "^$");
webApp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", "^$");
webApp.setDescriptor(new File(file, "/WEB-INF/web.xml").getAbsolutePath());
webApp.setResourceBase(file.getAbsolutePath());
handlerList.addHandler(webApp);
logger.debug("Deploying " + contextPath + " using exploded war " + file);
}
private void addStaticFolder(HandlerList handlerList, File file, String[] virtualHost) {
String contextPath = "/" + file.getName();
WebAppContext webApp = createWebAppContext(contextPath, virtualHost);
webApp.setWar(file.getAbsolutePath());
handlerList.addHandler(webApp);
logger.debug("Deploying " + contextPath + " using folder " + file);
}
private void addGetStartedApplication(HandlerList handlerList, File file, String[] virtualHost) {
if (file.exists()) {
String contextPath = "/";
WebAppContext webApp = createWebAppContext(contextPath, virtualHost);
webApp.setWar(file.getAbsolutePath());
handlerList.addHandler(webApp);
}
}
private WebAppContext createWebAppContext(String contextPath, String[] virtualHost) {
WebAppContext webApp = new WebAppContext();
webApp.setParentLoaderPriority(true);
webApp.setContextPath(contextPath);
webApp.setVirtualHosts(virtualHost);
return webApp;
}
private boolean isWarFile(File file) {
return "war".equals(FilenameUtils.getExtension(file.getName()));
}
private boolean isExplodedWebApp(File file) {
return file.isDirectory() && new File(file, "WEB-INF").exists();
}
private boolean isStaticFolder(File file) {
return file.isDirectory();
}
private void initializeRestProperties() {
System.setProperty(WebProperties.REST_HOME.getKey(), getSchedulerHome());
WebProperties.load();
if (!getSchedulerHome().equals(WebProperties.REST_HOME.getValueAsString())) {
throw new IllegalStateException("Rest home directory could not be initialized");
}
}
private String getSchedulerHome() {
if (PASchedulerProperties.SCHEDULER_HOME.isSet()) {
return PASchedulerProperties.SCHEDULER_HOME.getValueAsString();
} else {
return ".";
}
}
private void setSystemPropertyIfNotDefined(String key, String value) {
if (System.getProperty(key) == null) {
System.setProperty(key, value);
}
}
private String absolutePathOrRelativeToSchedulerHome(String path) {
if (new File(path).isAbsolute()) {
return path;
} else {
return new File(getSchedulerHome(), path).getPath();
}
}
}