/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.component;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.apache.activemq.artemis.ActiveMQWebLogger;
import org.apache.activemq.artemis.components.ExternalComponent;
import org.apache.activemq.artemis.dto.AppDTO;
import org.apache.activemq.artemis.dto.ComponentDTO;
import org.apache.activemq.artemis.dto.WebServerDTO;
import org.apache.activemq.artemis.utils.FileUtil;
import org.apache.activemq.artemis.utils.TimeUtils;
import org.eclipse.jetty.server.Connector;
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.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.webapp.WebAppContext;
import org.jboss.logging.Logger;
public class WebServerComponent implements ExternalComponent {
private static final Logger logger = Logger.getLogger(WebServerComponent.class);
private Server server;
private HandlerList handlers;
private WebServerDTO webServerConfig;
private URI uri;
private String jolokiaUrl;
private List<WebAppContext> webContexts;
@Override
public void configure(ComponentDTO config, String artemisInstance, String artemisHome) throws Exception {
webServerConfig = (WebServerDTO) config;
uri = new URI(webServerConfig.bind);
server = new Server();
String scheme = uri.getScheme();
ServerConnector connector = null;
if ("https".equals(scheme)) {
SslContextFactory sslFactory = new SslContextFactory();
sslFactory.setKeyStorePath(webServerConfig.keyStorePath == null ? artemisInstance + "/etc/keystore.jks" : webServerConfig.keyStorePath);
sslFactory.setKeyStorePassword(webServerConfig.keyStorePassword == null ? "password" : webServerConfig.keyStorePassword);
if (webServerConfig.clientAuth != null) {
sslFactory.setNeedClientAuth(webServerConfig.clientAuth);
if (webServerConfig.clientAuth) {
sslFactory.setTrustStorePath(webServerConfig.trustStorePath);
sslFactory.setTrustStorePassword(webServerConfig.trustStorePassword);
}
}
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslFactory, "HTTP/1.1");
HttpConfiguration https = new HttpConfiguration();
https.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory httpFactory = new HttpConnectionFactory(https);
connector = new ServerConnector(server, sslConnectionFactory, httpFactory);
} else {
connector = new ServerConnector(server);
}
connector.setPort(uri.getPort());
connector.setHost(uri.getHost());
server.setConnectors(new Connector[]{connector});
handlers = new HandlerList();
Path warDir = Paths.get(artemisHome != null ? artemisHome : ".").resolve(webServerConfig.path).toAbsolutePath();
if (webServerConfig.apps != null && webServerConfig.apps.size() > 0) {
webContexts = new ArrayList<>();
for (AppDTO app : webServerConfig.apps) {
WebAppContext webContext = deployWar(app.url, app.war, warDir);
webContexts.add(webContext);
if (app.war.startsWith("jolokia")) {
jolokiaUrl = webServerConfig.bind + "/" + app.url;
}
}
}
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(warDir.toString());
resourceHandler.setDirectoriesListed(true);
resourceHandler.setWelcomeFiles(new String[]{"index.html"});
DefaultHandler defaultHandler = new DefaultHandler();
defaultHandler.setServeIcon(false);
ContextHandler context = new ContextHandler();
context.setContextPath("/");
context.setResourceBase(warDir.toString());
context.setHandler(resourceHandler);
handlers.addHandler(context);
handlers.addHandler(defaultHandler);
server.setHandler(handlers);
}
@Override
public void start() throws Exception {
if (isStarted()) {
return;
}
server.start();
ActiveMQWebLogger.LOGGER.webserverStarted(webServerConfig.bind);
if (jolokiaUrl != null) {
ActiveMQWebLogger.LOGGER.jolokiaAvailable(jolokiaUrl);
}
}
public void internalStop() throws Exception {
server.stop();
if (webContexts != null) {
File tmpdir = null;
for (WebAppContext context : webContexts) {
tmpdir = context.getTempDirectory();
if (tmpdir != null && !context.isPersistTempDirectory()) {
//tmpdir will be removed by deleteOnExit()
//somehow when broker is stopped and restarted quickly
//this tmpdir won't get deleted sometimes
boolean fileDeleted = TimeUtils.waitOnBoolean(false, 5000, tmpdir::exists);
if (!fileDeleted) {
//because the execution order of shutdown hooks are
//not determined, so it's possible that the deleteOnExit
//is executed after this hook, in that case we force a delete.
FileUtil.deleteDirectory(tmpdir);
logger.debug("Force to delete temporary file on shutdown: " + tmpdir.getAbsolutePath());
if (tmpdir.exists()) {
ActiveMQWebLogger.LOGGER.tmpFileNotDeleted(tmpdir);
}
}
}
}
webContexts.clear();
}
}
@Override
public boolean isStarted() {
return server != null && server.isStarted();
}
private WebAppContext deployWar(String url, String warFile, Path warDirectory) throws IOException {
WebAppContext webapp = new WebAppContext();
if (url.startsWith("/")) {
webapp.setContextPath(url);
} else {
webapp.setContextPath("/" + url);
}
webapp.setWar(warDirectory.resolve(warFile).toString());
handlers.addHandler(webapp);
return webapp;
}
@Override
public void stop() throws Exception {
stop(false);
}
@Override
public void exit() throws Exception {
stop(true);
}
public void stop(boolean isShutdown) throws Exception {
if (isShutdown) {
internalStop();
}
}
}