/*
* 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.isis.core.webserver;
import java.io.File;
import java.net.URI;
import java.util.Arrays;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Lists;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.core.commons.config.ConfigurationConstants;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.commons.configbuilder.IsisConfigurationBuilder;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.commons.lang.ArrayExtensions;
import org.apache.isis.core.commons.lang.ObjectExtensions;
import org.apache.isis.core.commons.resource.ResourceStreamSource;
import org.apache.isis.core.commons.resource.ResourceStreamSourceContextLoaderClassPath;
import org.apache.isis.core.commons.resource.ResourceStreamSourceFileSystem;
import org.apache.isis.core.runtime.logging.IsisLoggingConfigurer;
import org.apache.isis.core.runtime.optionhandler.OptionHandler;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerAdditionalProperty;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerAppManifest;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerConfiguration;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerFixture;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerFixtureFromEnvironmentVariable;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerHelp;
import org.apache.isis.core.runtime.runner.opts.OptionHandlerSystemProperties;
import org.apache.isis.core.webapp.WebAppConstants;
import org.apache.isis.core.webserver.internal.OptionHandlerDeploymentTypeWebServer;
import org.apache.isis.core.webserver.internal.OptionHandlerPort;
import org.apache.isis.core.webserver.internal.OptionHandlerStartupMode;
import static org.apache.isis.core.webserver.WebServerConstants.EMBEDDED_WEB_SERVER_PORT_DEFAULT;
import static org.apache.isis.core.webserver.WebServerConstants.EMBEDDED_WEB_SERVER_PORT_KEY;
import static org.apache.isis.core.webserver.WebServerConstants.EMBEDDED_WEB_SERVER_RESOURCE_BASE_DEFAULT;
import static org.apache.isis.core.webserver.WebServerConstants.EMBEDDED_WEB_SERVER_RESOURCE_BASE_KEY;
import static org.apache.isis.core.webserver.WebServerConstants.EMBEDDED_WEB_SERVER_STARTUP_MODE_DEFAULT;
import static org.apache.isis.core.webserver.WebServerConstants.EMBEDDED_WEB_SERVER_STARTUP_MODE_KEY;
public class WebServer {
private static final Logger LOG = LoggerFactory.getLogger(WebServer.class);
private static final String SRC_MAIN_WEBAPP = "src/main/webapp";
public enum StartupMode {
FOREGROUND, BACKGROUND;
public static StartupMode lookup(final String value) {
if (value == null) {
return null;
}
try {
return valueOf(value.toUpperCase());
} catch (final Exception e) {
return null;
}
}
public boolean isForeground() {
return this == FOREGROUND;
}
public boolean isBackground() {
return this == BACKGROUND;
}
}
private Server jettyServer;
public static void main(final String[] args) {
new WebServer().run(args);
}
public void run(final int port) {
String[] args = new String[0];
args = OptionHandlerStartupMode.appendArg(args, StartupMode.BACKGROUND);
args = OptionHandlerPort.appendArg(args, port);
run(args);
}
private final IsisLoggingConfigurer loggingConfigurer = new IsisLoggingConfigurer();
public void run(final String[] args) {
// setup logging immediately
loggingConfigurer.configureLogging(guessConfigDirectory(), args);
// set up the configuration
final IsisConfigurationBuilder isisConfigurationBuilder = new IsisConfigurationBuilder();
isisConfigurationBuilder.addResourceStreamSources(resourceStreamSources());
if(!isisConfigurationBuilder.parseAndPrimeWith(standardHandlers(), args)) {
return;
}
// create and start
jettyServer = createJettyServerAndBindConfig(isisConfigurationBuilder);
final IsisConfiguration configuration = isisConfigurationBuilder.peekConfiguration();
final String startupModeStr = configuration.getString(
EMBEDDED_WEB_SERVER_STARTUP_MODE_KEY, EMBEDDED_WEB_SERVER_STARTUP_MODE_DEFAULT);
final StartupMode startupMode = StartupMode.lookup(startupModeStr);
start(jettyServer, startupMode);
}
private static List<OptionHandler> standardHandlers() {
return Lists.newArrayList(
new OptionHandlerConfiguration(),
new OptionHandlerFixture(),
new OptionHandlerAppManifest(),
new OptionHandlerAdditionalProperty(),
new OptionHandlerFixtureFromEnvironmentVariable(),
new OptionHandlerSystemProperties(),
new OptionHandlerHelp(),
new OptionHandlerPort(),
new OptionHandlerDeploymentTypeWebServer()
);
}
// REVIEW: hacky...
private static String guessConfigDirectory() {
return new File(ConfigurationConstants.WEBINF_FULL_DIRECTORY).exists() ?
ConfigurationConstants.WEBINF_FULL_DIRECTORY :
ConfigurationConstants.DEFAULT_CONFIG_DIRECTORY;
}
private Server createJettyServerAndBindConfig(final IsisConfigurationBuilder configurationBuilder) {
// the Isis system is actually bootstrapped by the ServletContextInitializer in the web.xml
final IsisConfiguration configuration = configurationBuilder.peekConfiguration();
final int port = configuration.getInteger(
EMBEDDED_WEB_SERVER_PORT_KEY, EMBEDDED_WEB_SERVER_PORT_DEFAULT);
final String webappContextPath = configuration.getString(
EMBEDDED_WEB_SERVER_RESOURCE_BASE_KEY, EMBEDDED_WEB_SERVER_RESOURCE_BASE_DEFAULT);
LOG.info("Running Jetty on port '{}' to serve the web application", port);
final Server jettyServer = new Server(port);
final WebAppContext context = new WebAppContext(SRC_MAIN_WEBAPP, webappContextPath);
jettyServer.setHandler(context);
context.setAttribute(WebAppConstants.CONFIGURATION_BUILDER_KEY, configurationBuilder);
return jettyServer;
}
private static void start(final Server jettyServer, final StartupMode startupMode) {
long start = System.currentTimeMillis();
try {
jettyServer.start();
LOG.info("Started the application in {}ms", System.currentTimeMillis() - start);
if (startupMode.isForeground()) {
System.in.read();
System.out.println(">>> STOPPING EMBEDDED JETTY SERVER");
jettyServer.stop();
jettyServer.join();
}
} catch (final Exception ex) {
throw new IsisException("Unable to start Jetty server", ex);
}
}
@SuppressWarnings("unused")
private void copyDeploymentTypeIntoInitParams(final WebAppContext context) {
Map<String, String> initParams = context.getInitParams();
Map<String, String> convertedInitParams = ObjectExtensions.asT(initParams);
initParams.clear();
initParams.putAll(convertedInitParams);
}
/**
* Set of locations to search for config files.
*/
private static List<ResourceStreamSource> resourceStreamSources() {
final List<ResourceStreamSource> rssList = Lists.newArrayList();
rssList.addAll(Arrays.asList(
ResourceStreamSourceFileSystem.create(ConfigurationConstants.DEFAULT_CONFIG_DIRECTORY),
ResourceStreamSourceFileSystem.create(ConfigurationConstants.WEBINF_FULL_DIRECTORY),
ResourceStreamSourceContextLoaderClassPath.create(),
ResourceStreamSourceContextLoaderClassPath.create(ConfigurationConstants.WEBINF_DIRECTORY)));
return rssList;
}
public void stop() {
if (jettyServer == null) {
return;
}
try {
jettyServer.stop();
} catch (final Exception e) {
e.printStackTrace(System.err);
}
}
public URI getBase() {
return URI.create(baseFor(jettyServer));
}
private String baseFor(final Server jettyServer) {
final ServerConnector connector = (ServerConnector) jettyServer.getConnectors()[0];
final String scheme = "http";
final String host = ArrayExtensions.coalesce(connector.getHost(), "localhost");
final int port = connector.getPort();
final WebAppContext handler = (WebAppContext) jettyServer.getHandler();
final String contextPath = handler.getContextPath();
final StringBuilder buf = new StringBuilder();
try(final Formatter formatter = new Formatter(buf)) {
formatter.format("%s://%s:%d/%s", scheme, host, port, contextPath);
}
return appendSlashIfRequired(buf).toString();
}
private static StringBuilder appendSlashIfRequired(final StringBuilder buf) {
if (buf.charAt(buf.length() - 1) != '/') {
buf.append('/');
}
return buf;
}
}