All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ninja.standalone.AbstractStandalone Maven / Gradle / Ivy

/**
 * Copyright (C) 2012-2016 the original author or authors.
 *
 * 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 ninja.standalone;

import ninja.utils.OverlayedNinjaProperties;
import com.google.common.base.Optional;
import com.google.inject.CreationException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.SSLContext;
import static ninja.standalone.StandaloneHelper.checkContextPath;
import ninja.utils.NinjaConstant;
import ninja.utils.NinjaMode;
import ninja.utils.NinjaModeHelper;
import ninja.utils.NinjaPropertiesImpl;
import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract Standalone that implements most functionality required to write
 * a concrete Standalone.  Introduces new doStart(), doStop(), and doJoin()
 * methods which are actually where you'll place most of your logic.  You'll
 * also want to subclass the configure() method to add any configuration
 * specific to your Standalone.  See NinjaJetty for example concrete implementation.
 * @param  The concrete standalone implementation
 */
abstract public class AbstractStandalone implements Standalone, Runnable {
    // allow logger to take on persona of concrete class
    final protected Logger logger = LoggerFactory.getLogger(this.getClass());

    // can all be changed prior to configure()
    protected NinjaMode ninjaMode;
    protected String externalConfigurationPath;
    protected String name;
    protected String host;
    protected Integer port;
    protected String contextPath;
    protected Long idleTimeout;
    protected Integer sslPort;
    protected URI sslKeystoreUri;
    protected String sslKeystorePassword;
    protected URI sslTruststoreUri;
    protected String sslTruststorePassword;
    
    // internal state
    protected boolean configured;
    protected boolean started;
    protected NinjaPropertiesImpl ninjaProperties;                      // after configure()
    protected OverlayedNinjaProperties overlayedNinjaProperties;        // after configure()
    protected List serverUrls;
    protected List baseUrls;
    
    public AbstractStandalone(String name) {
        // set mode as quickly as possible (can still be changed before configure())
        this.ninjaMode = NinjaModeHelper.determineModeFromSystemPropertiesOrProdIfNotSet();
        this.name = name;
        this.configured = false;
        this.started = false;
    }
    
    /**
     * Configure, start, add shutdown hook, and join.  Does not exit.
     */
    @Override
    final public void run() {
        // name current thread for improved logging/debugging
        Thread.currentThread().setName(this.name);
        
        try {
            this.configure();
        } catch (Exception e) {
            logger.error("Unable to configure {}", name, e);
            System.exit(1);
        }
 
        try {
            this.start();
        } catch (Exception e) {
            logger.error("Unable to start {}", name, e);
            System.exit(1);
        }
        
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                shutdown();
            }
        });
        
        try {
            // do not simply exit main() -- join something (likely server)
            join();
        } catch (Exception e) {
            logger.warn("Interrupted (most likely JVM is shutting down and this is safe to ignore)");
        }
    }
    
    @Override
    final public T configure() throws Exception {
        checkNotConfigured();
        
        // create ninja properties & overlayed view
        this.ninjaProperties = new NinjaPropertiesImpl(this.ninjaMode, this.externalConfigurationPath);
        
        this.overlayedNinjaProperties = new OverlayedNinjaProperties(this.ninjaProperties);
        
        // current value or system property or conf/application.conf or default value
        host(overlayedNinjaProperties.get(
                KEY_NINJA_HOST, this.host, DEFAULT_HOST));
        
        port(overlayedNinjaProperties.getInteger(
                KEY_NINJA_PORT, this.port, DEFAULT_PORT));
        
        contextPath(overlayedNinjaProperties.get(
                KEY_NINJA_CONTEXT_PATH, this.contextPath, DEFAULT_CONTEXT_PATH));
        
        idleTimeout(overlayedNinjaProperties.getLong(
                KEY_NINJA_IDLE_TIMEOUT, this.idleTimeout, DEFAULT_IDLE_TIMEOUT));
        
        sslPort(overlayedNinjaProperties.getInteger(
                KEY_NINJA_SSL_PORT, this.sslPort, DEFAULT_SSL_PORT));
        
        // defaults below (with self-signed cert) only valid in dev & test modes
        
        sslKeystoreUri(overlayedNinjaProperties.getURI(KEY_NINJA_SSL_KEYSTORE_URI, this.sslKeystoreUri, 
                (this.ninjaMode == NinjaMode.prod ? null : new URI(DEFAULT_DEV_NINJA_SSL_KEYSTORE_URI))));
        
        sslKeystorePassword(overlayedNinjaProperties.get(
                KEY_NINJA_SSL_KEYSTORE_PASSWORD, this.sslKeystorePassword, 
                (this.ninjaMode == NinjaMode.prod ? null : DEFAULT_DEV_NINJA_SSL_KEYSTORE_PASSWORD)));
        
        sslTruststoreUri(overlayedNinjaProperties.getURI(KEY_NINJA_SSL_TRUSTSTORE_URI, this.sslTruststoreUri, 
                (this.ninjaMode == NinjaMode.prod ? null : new URI(DEFAULT_DEV_NINJA_SSL_TRUSTSTORE_URI))));
        
        sslTruststorePassword(overlayedNinjaProperties.get(
                KEY_NINJA_SSL_TRUSTSTORE_PASSWORD, this.sslTruststorePassword, 
                (this.ninjaMode == NinjaMode.prod ? null : DEFAULT_DEV_NINJA_SSL_TRUSTSTORE_PASSWORD)));
        
        
        // assign random ports if needed
        if (getPort() == null || getPort() == 0) {
            port(StandaloneHelper.findAvailablePort(8000, 9000));
        }
        
        if (getSslPort() == null || getSslPort() == 0) {
            sslPort(StandaloneHelper.findAvailablePort(9001, 9999));
        }
        
        doConfigure();
        
        this.configured = true;
        
        // build configured urls
        this.serverUrls = createServerUrls();
        this.baseUrls = createBaseUrls();
        
        // is there at least one url?
        if (this.serverUrls == null || this.serverUrls.isEmpty()) {
            throw new IllegalStateException("All server ports were disabled." + 
                    " Check the 'ninja.port' property and possibly others depending your standalone.");
        }
        
        // save generated server name as ninja property if its not yet set
        String serverName = this.ninjaProperties.get(NinjaConstant.serverName);
        
        if (StringUtils.isEmpty(serverName)) {
            // grab the first one
            this.ninjaProperties.setProperty(NinjaConstant.serverName, getServerUrls().get(0));
        }
        
        return (T)this;
    }
    
    @Override
    final public T start() throws Exception {
        if (!this.configured) {
            configure();
        }
        
        doStart();
        
        this.started = true;
        
        logBaseUrls();
        
        return (T)this;
    }
    
    @Override
    final public T join() throws Exception{
        checkStarted();
        
        doJoin();
        
        return (T)this;
    }
    
    @Override
    final public T shutdown() {
        doShutdown();
        return (T)this;
    }
    
    abstract protected void doConfigure() throws Exception;
    
    abstract protected void doStart() throws Exception;
    
    abstract protected void doJoin() throws Exception;
    
    abstract protected void doShutdown();
    
    protected void checkNotConfigured() {
        if (this.configured) {
            throw new IllegalStateException(this.getClass().getCanonicalName() + ".configure() already called");
        }
    }
    
    protected void checkConfigured() {
        if (!this.configured) {
            throw new IllegalStateException(this.getClass().getCanonicalName() + ".configure() not called yet");
        }
    }
    
    protected void checkStarted() {
        if (!this.started) {
            throw new IllegalStateException(this.getClass().getCanonicalName() + ".start() not called yet");
        }
    }
    
    // semi-builder pattern
    
    @Override
    public NinjaMode getNinjaMode() {
        return ninjaMode;
    }
    
    @Override
    public T ninjaMode(NinjaMode ninjaMode) {
        this.ninjaMode = ninjaMode;
        return (T)this;
    }
    
    @Override
    public String getExternalConfigurationPath() {
        return this.externalConfigurationPath;
    }
    
    @Override
    public T externalConfigurationPath(String externalConfigurationPath) {
        this.externalConfigurationPath = externalConfigurationPath;
        return (T)this;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public T name(String name) {
        this.name = name;
        return (T)this;
    }
    
    @Override
    public Integer getPort() {
        return this.port;
    }
    
    @Override
    public T port(int port) {
        this.port = port;
        return (T)this;
    }

    @Override
    public String getHost() {
        return this.host;
    }
    
    @Override
    public T host(String host) {
        this.host = host;
        return (T)this;
    }
    
    @Override
    public Long getIdleTimeout() {
        return idleTimeout;
    }
    
    @Override
    public T idleTimeout(long idleTimeout) {
        this.idleTimeout = idleTimeout;
        return (T)this;
    }

    @Override
    public String getContextPath() {
        return contextPath;
    }
    
    @Override
    public T contextPath(String contextPath) {
        checkContextPath(contextPath);
        this.contextPath = contextPath;
        return (T)this;
    }
    
    @Override
    public Integer getSslPort() {
        return this.sslPort;
    }
    
    @Override
    public T sslPort(int sslPort) {
        this.sslPort = sslPort;
        return (T)this;
    }

    @Override
    public URI getSslKeystoreUri() {
        return this.sslKeystoreUri;
    }

    @Override
    public T sslKeystoreUri(URI keystoreUri) {
        this.sslKeystoreUri = keystoreUri;
        return (T)this;
    }

    @Override
    public String getSslKeystorePassword() {
        return this.sslKeystorePassword;
    }

    @Override
    public T sslKeystorePassword(String keystorePassword) {
        this.sslKeystorePassword = keystorePassword;
        return (T)this;
    }

    @Override
    public URI getSslTruststoreUri() {
        return this.sslTruststoreUri;
    }

    @Override
    public T sslTruststoreUri(URI truststoreUri) {
        this.sslTruststoreUri = truststoreUri;
        return (T)this;
    }

    @Override
    public String getSslTruststorePassword() {
        return this.sslTruststorePassword;
    }

    @Override
    public T sslTruststorePassword(String truststorePassword) {
        this.sslTruststorePassword = truststorePassword;
        return (T)this;
    }
    
    @Override
    public NinjaPropertiesImpl getNinjaProperties() {
        // only available after configure()
        checkConfigured();
        return ninjaProperties;
    }
    
    @Override
    public List getServerUrls() {
        // only available after configure()
        checkConfigured();
        return serverUrls;
    }
    
    @Override
    public List getBaseUrls() {
        // only available after configure()
        checkConfigured();
        return baseUrls;
    }
    
    @Override
    public boolean isPortEnabled() {
        return this.port != null && this.port > -1;
    }
    
    @Override
    public boolean isSslPortEnabled() {
        return this.sslPort != null && this.sslPort > -1;
    }
    
    protected List createServerUrls() {
        // only available after configure()
        checkConfigured();
        
        List urls = new ArrayList<>();
        
        if (isPortEnabled()) {
            urls.add(createServerUrl("http", getHost(), getPort()));
        }
        
        if (isSslPortEnabled()) {
            urls.add(createServerUrl("https", getHost(), getSslPort()));
        }
        
        return urls;
    }
    
    protected List createBaseUrls() {
        // only available after configure()
        checkConfigured();
        
        List urls = new ArrayList<>();
        
        if (isPortEnabled()) {
            urls.add(createBaseUrl("http", getHost(), getPort(), getContextPath()));
        }
        
        if (isSslPortEnabled()) {
            urls.add(createBaseUrl("https", getHost(), getSslPort(), getContextPath()));
        }
        
        return urls;
    }
    
    // helpful utilities for subclasses
    
    protected String createServerUrl(String scheme, String host, Integer port) {
        StringBuilder sb = new StringBuilder();
        
        sb.append(scheme);
        sb.append("://");
        sb.append((host != null ? host : "localhost"));
        
        if (("http".equals(scheme) && port != 80) || ("https".equals(scheme) && port != 443)) {
            sb.append(":");
            sb.append(port);
        }
        
        return sb.toString();
    }
    
    protected String createBaseUrl(String scheme, String host, Integer port, String context) {
        StringBuilder sb = new StringBuilder();
        
        sb.append(createServerUrl(scheme, host, port));
        
        if (StringUtils.isNotEmpty(context)) {
            sb.append(context);
        }
        
        return sb.toString();
    }
    
    protected Exception tryToUnwrapInjectorException(Exception exception) {
        Throwable cause = exception.getCause();
        if (cause != null && cause instanceof CreationException) {
            return (CreationException)cause;
        } else {
            return exception;
        }
    }
    
    protected String getLoggableIdentifier() {
        // build list of ports
        StringBuilder ports = new StringBuilder();
        
        if (isPortEnabled()) {
            ports.append(getPort());
        }
        
        if (isSslPortEnabled()) {
            if (ports.length() > 0) {
                ports.append(", ");
            }
            ports.append(getSslPort());
            ports.append("/ssl");
        }
        
        StringBuilder s = new StringBuilder();
        
        s.append("on ");
        
        s.append(Optional.fromNullable(getHost()).or(""));
        s.append(":");
        s.append(ports);

        return s.toString();
    }
    
    protected void logBaseUrls() {
        logger.info("-------------------------------------------------------");
        logger.info("Ninja application running at");
        
        List uris = getBaseUrls();
        for (String uri : uris) {
            logger.info(" => {}", uri);
        }
        
        logger.info("-------------------------------------------------------");
    }
    
    protected SSLContext createSSLContext() throws Exception {
        if (this.sslKeystoreUri == null) {
            throw new IllegalStateException("Unable to create SSL context. Configuration key " + KEY_NINJA_SSL_KEYSTORE_URI
                    + " has empty value.  Please check your configuration file.");
        }
        
        if (this.sslKeystorePassword == null) {
            throw new IllegalStateException("Unable to create SSL context. Configuration key " + KEY_NINJA_SSL_KEYSTORE_PASSWORD
                    + " has empty value.  Please check your configuration file.");
        }
        
        if (this.sslTruststoreUri == null) {
            throw new IllegalStateException("Unable to create SSL context. Configuration key " + KEY_NINJA_SSL_TRUSTSTORE_URI
                    + " has empty value.  Please check your configuration file.");
        }
        
        if (this.sslTruststorePassword == null) {
            throw new IllegalStateException("Unable to create SSL context. Configuration key " + KEY_NINJA_SSL_TRUSTSTORE_PASSWORD
                    + " has empty value.  Please check your configuration file.");
        }
        
        return StandaloneHelper.createSSLContext(this.sslKeystoreUri, this.sslKeystorePassword.toCharArray(),
            this.sslTruststoreUri, this.sslTruststorePassword.toCharArray());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy