com.hypersocket.server.HypersocketServerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.hypersocket.server.HypersocketServerImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2013 Hypersocket Limited.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 ******************************************************************************/
package com.hypersocket.server;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;

import com.hypersocket.certificates.CertificateResourceService;
import com.hypersocket.config.ConfigurationChangedEvent;
import com.hypersocket.config.SystemConfigurationService;
import com.hypersocket.events.EventService;
import com.hypersocket.events.SystemEvent;
import com.hypersocket.i18n.I18NService;
import com.hypersocket.permissions.AccessDeniedException;
import com.hypersocket.realm.RealmService;
import com.hypersocket.server.events.ServerStartedEvent;
import com.hypersocket.server.events.ServerStartingEvent;
import com.hypersocket.server.events.ServerStoppedEvent;
import com.hypersocket.server.events.ServerStoppingEvent;
import com.hypersocket.server.events.WebappCreatedEvent;
import com.hypersocket.server.handlers.HttpRequestHandler;
import com.hypersocket.server.handlers.WebsocketHandler;
import com.hypersocket.server.handlers.impl.APIRequestHandler;
import com.hypersocket.servlet.HypersocketServletConfig;
import com.hypersocket.servlet.HypersocketSession;
import com.hypersocket.servlet.HypersocketSessionFactory;

public abstract class HypersocketServerImpl implements HypersocketServer, ApplicationListener<SystemEvent> {

    private static Logger log = LoggerFactory.getLogger(HypersocketServerImpl.class);

    private Map<String, Object> attributes = new HashMap<String, Object>();

    private String sessionCookieName;

    public static final String API_PATH = "api";

    private AnnotationConfigWebApplicationContext webappContext;
    private DispatcherServlet dispatcherServlet;
    private HypersocketServletConfig servletConfig;

    private List<String> controllerPackages = new ArrayList<String>();

    @Autowired
    EventService eventService;

    List<HttpRequestHandler> httpHandlers = Collections.synchronizedList(new ArrayList<HttpRequestHandler>());

    List<WebsocketHandler> wsHandlers = Collections.synchronizedList(new ArrayList<WebsocketHandler>());

    List<String> compressablePaths = new ArrayList<String>();

    ApplicationContext applicationContext;

    @Autowired
    SystemConfigurationService configurationService;

    @Autowired
    RealmService realmService;

    @Autowired
    SessionFactory sessionFactory;

    SSLContext defaultSSLContext;
    String[] enabledCipherSuites;
    String[] enabledProtocols;

    boolean stopping = false;

    public HypersocketServerImpl() {
        Security.addProvider(new BouncyCastleProvider());
        controllerPackages.add("com.hypersocket.json.**");
        controllerPackages.add("com.hypersocket.**.json");
    }

    @Override
    public void registerControllerPackage(String controllerPackage) {
        controllerPackages.add(controllerPackage);
    }

    @Override
    public boolean isPlainPort(InetSocketAddress localAddress) {
        if (Boolean.getBoolean("hypersocket.development")) {
            return localAddress.getPort() == 8080;
        } else {
            if (configurationService.getIntValue("http.port") > 0) {
                return localAddress.getPort() == configurationService.getIntValue("http.port");
            }
            return true;
        }
    }

    @Override
    public boolean isSSLPort(InetSocketAddress localAddress) {
        if (Boolean.getBoolean("hypersocket.development")) {
            return localAddress.getPort() == 8443;
        } else {
            return localAddress.getPort() == configurationService.getIntValue("https.port");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getHttpPort()
     */
    @Override
    public int getHttpPort() {
        return Boolean.getBoolean("hypersocket.development") ? 8080 : configurationService.getIntValue("http.port");
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getHttpsPort()
     */
    @Override
    public int getHttpsPort() {
        return Boolean.getBoolean("hypersocket.development") ? 8443
                : configurationService.getIntValue("https.port");
    }

    public SSLContext getSSLContext(InetSocketAddress localAddress, InetSocketAddress remoteAddress) {
        // TODO lookup context based on localAddress / maybe even remote address????
        return defaultSSLContext;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getHttpHandlers()
     */
    @Override
    public List<HttpRequestHandler> getHttpHandlers() {
        return httpHandlers;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getWebsocketHandlers()
     */
    @Override
    public List<WebsocketHandler> getWebsocketHandlers() {
        return wsHandlers;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.hypersocket.server.HypersocketServer#registerHttpHandler(com.hypersocket
     * .server.HttpRequestHandler)
     */
    @Override
    public void registerHttpHandler(HttpRequestHandler handler) {
        handler.setServer(this);
        httpHandlers.add(handler);
        Collections.sort(httpHandlers);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#unregisterHttpHandler(com.
     * hypersocket.server.HttpRequestHandler)
     */
    @Override
    public void unregisterHttpHandler(HttpRequestHandler handler) {
        httpHandlers.remove(handler);
        Collections.sort(httpHandlers);
    }

    public void init(ApplicationContext applicationContext)
            throws AccessDeniedException, IOException, ServletException {

        this.applicationContext = applicationContext;

        eventService.registerEvent(ServerStartingEvent.class, RESOURCE_BUNDLE);
        eventService.registerEvent(ServerStartedEvent.class, RESOURCE_BUNDLE);
        eventService.registerEvent(ServerStoppingEvent.class, RESOURCE_BUNDLE);
        eventService.registerEvent(ServerStoppedEvent.class, RESOURCE_BUNDLE);
        eventService.registerEvent(WebappCreatedEvent.class, RESOURCE_BUNDLE);

        eventService.publishEvent(new ServerStartingEvent(this, realmService.getSystemPrincipal().getRealm()));

        initializeSSL();

        registerConfiguration();

        rebuildEnabledCipherSuites();

        createWebappContext();

    }

    private void createWebappContext() throws ServletException {
        if (log.isDebugEnabled())
            log.debug("Creating spring webapp context");

        servletConfig = new HypersocketServletConfig("default", resolvePath(API_PATH));

        webappContext = new AnnotationConfigWebApplicationContext();
        webappContext.setParent(applicationContext);
        webappContext.register(DelegatingWebMvcConfiguration.class);
        webappContext.scan(controllerPackages.toArray(new String[0]));

        webappContext.setServletConfig(servletConfig);
        webappContext.refresh();
        webappContext.start();

        // We use a custom implementation of DispatcherServlet so it does not restrict the HTTP methods
        dispatcherServlet = new NonRestrictedDispatcherServlet(webappContext);
        dispatcherServlet.init(servletConfig);

        registerHttpHandler(new APIRequestHandler(dispatcherServlet, 100));

        eventService.publishEvent(new WebappCreatedEvent(this, true, realmService.getSystemRealm()));

    }

    @Override
    public void registerWebsocketpHandler(WebsocketHandler wsHandler) {
        wsHandlers.add(wsHandler);
    }

    protected void registerConfiguration() throws AccessDeniedException, IOException {

        I18NService i18nService = (I18NService) applicationContext.getBean("i18NServiceImpl");
        i18nService.registerBundle(RESOURCE_BUNDLE);

    }

    @Override
    public boolean isHttpsRequired() {
        return configurationService.getBooleanValue("require.https");
    }

    @Override
    public String[] getSSLProtocols() {
        SSLEngine engine = defaultSSLContext.createSSLEngine();
        return engine.getSupportedProtocols();
    }

    @Override
    public String[] getSSLCiphers() {
        SSLEngine engine = defaultSSLContext.createSSLEngine();
        return engine.getSupportedCipherSuites();
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.hypersocket.server.HypersocketServer#setAttribute(java.lang.String,
     * java.lang.Object)
     */
    @Override
    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.hypersocket.server.HypersocketServer#getAttribute(java.lang.String,
     * T)
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getAttribute(String name, T template) {
        if (attributes.containsKey(name)) {
            return (T) attributes.get(name);
        } else {
            return template;
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getApplicationName()
     */
    @Override
    public String getApplicationName() {
        return configurationService.getValue("application.name").trim();
    }

    @Override
    public String resolvePath(String path) {
        if (path == null) {
            return getBasePath();
        } else {
            return getBasePath() + (!path.startsWith("/") ? "/" : "") + path;
        }
    }

    @Override
    public String getBasePath() {
        return "/" + configurationService.getValue("application.path").trim();
    }

    @Override
    public String getUiPath() {
        return resolvePath(getUserInterfacePath());
    }

    @Override
    public String getApiPath() {
        return resolvePath(API_PATH);
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#start()
     */
    @Override
    public void start() throws IOException {

        if (log.isInfoEnabled())
            log.info("Starting server");

        doStart();

        if (log.isInfoEnabled())
            log.info("Server started");

        System.setProperty("hypersocket.appPath", getBasePath());
        System.setProperty("hypersocket.uiPath", getUiPath());

        eventService.publishEvent(new ServerStartedEvent(this, realmService.getSystemRealm()));

    }

    protected abstract void doStart() throws IOException;

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#stop()
     */
    @Override
    public void stop() {

        stopping = true;

        eventService.publishEvent(new ServerStoppingEvent(this, realmService.getSystemRealm()));

        if (log.isInfoEnabled())
            log.info("Stopping server");

        doStop();

        if (log.isInfoEnabled())
            log.info("Server stopped");

        eventService.publishEvent(new ServerStoppedEvent(this, realmService.getSystemRealm()));

    }

    public boolean isStopping() {
        return stopping;
    }

    protected abstract void doStop();

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getDispatcherServlet()
     */
    @Override
    public DispatcherServlet getDispatcherServlet() {
        return dispatcherServlet;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getServletConfig()
     */
    @Override
    public ServletConfig getServletConfig() {
        return servletConfig;
    }

    public HypersocketSession setupHttpSession(List<String> cookies, boolean secure,
            HttpServletResponse servletResponse) {

        HypersocketSession session = null;

        for (String header : cookies) {

            StringTokenizer t = new StringTokenizer(header, ";");

            while (t.hasMoreTokens()) {
                String cookie = t.nextToken();
                int idx = cookie.indexOf('=');
                if (idx == -1)
                    continue;
                String name = cookie.substring(0, idx).trim();
                if (name.equals(sessionCookieName)) {
                    String value = cookie.substring(idx + 1);
                    session = HypersocketSessionFactory.getInstance().getSession(value,
                            servletConfig.getServletContext());
                    // Check that the session exists in case we have any old
                    // cookies
                    if (session != null) {
                        break;
                    }
                }
            }
        }
        if (session == null) {
            session = HypersocketSessionFactory.getInstance().createSession(servletConfig.getServletContext());
        }

        Cookie cookie = new Cookie(sessionCookieName, session.getId());
        cookie.setMaxAge(60 * 15);
        cookie.setPath("/");
        cookie.setSecure(secure);
        servletResponse.addCookie(cookie);

        return session;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.hypersocket.server.HypersocketServer#getApplicationPath()
     */
    @Override
    public String getApplicationPath() {
        return getBasePath();
    }

    public void initializeSSL() throws FileNotFoundException, IOException {

        CertificateResourceService certificateService = (CertificateResourceService) applicationContext
                .getBean("certificateResourceServiceImpl");
        RealmService realmService = (RealmService) applicationContext.getBean("realmServiceImpl");

        certificateService.setCurrentPrincipal(realmService.getSystemPrincipal(), Locale.getDefault(),
                realmService.getSystemPrincipal().getRealm());

        try {

            if (log.isInfoEnabled()) {
                log.info("Initializing SSL contexts");
            }

            KeyStore ks = certificateService.getDefaultCertificate();

            // Get the default context
            defaultSSLContext = SSLContext.getInstance("TLS");

            // KeyManager's decide which key material to use.
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(ks, "changeit".toCharArray());
            defaultSSLContext.init(kmf.getKeyManagers(), null, null);

            if (log.isInfoEnabled()) {
                log.info("Completed SSL initialization");
            }
        } catch (Exception ex) {
            log.error("SSL initalization failed", ex);
            throw new IOException("SSL initialization failed: " + ex.getMessage());
        } finally {
            certificateService.clearPrincipalContext();
        }
    }

    private void rebuildEnabledCipherSuites() {

        try {
            enabledProtocols = configurationService.getValues("ssl.protocols");
        } catch (IllegalStateException ex) {
        }

        try {
            String[] ciphers = configurationService.getValues("ssl.ciphers");
            SSLEngine engine = defaultSSLContext.createSSLEngine();

            if (ciphers != null && ciphers.length > 0) {

                List<String> enabledCSList = new ArrayList<String>(Arrays.asList(engine.getEnabledCipherSuites()));
                List<String> includedCSList = new ArrayList<String>();

                boolean hasValid = false;
                for (String cipherName : ciphers) {
                    if (enabledCSList.contains(cipherName)) {
                        includedCSList.add(cipherName);
                        hasValid = true;
                    } else {
                        if (log.isInfoEnabled()) {
                            log.warn("Disabled cipher: " + cipherName);
                        }
                    }
                }
                enabledCipherSuites = (String[]) includedCSList.toArray(new String[includedCSList.size()]);

                if (!hasValid) {
                    if (log.isWarnEnabled()) {
                        log.warn("SSL cipher list did not contain any valid ciphers. Reverting to defaults.");
                    }
                    enabledCipherSuites = engine.getEnabledCipherSuites();
                } else {
                    for (String cipher : enabledCipherSuites) {
                        if (log.isInfoEnabled()) {
                            log.info("Enabled cipher: " + cipher);
                        }
                    }
                }
            }
        } catch (IllegalStateException ex) {

        }
    }

    public SSLEngine createSSLEngine(InetSocketAddress localAddress, InetSocketAddress remoteAddress) {

        SSLEngine engine = getSSLContext(localAddress, remoteAddress).createSSLEngine();

        engine.setUseClientMode(false);
        engine.setWantClientAuth(false);

        if (enabledCipherSuites != null && enabledCipherSuites.length > 0) {
            engine.setEnabledCipherSuites(enabledCipherSuites);
        }
        if (enabledProtocols != null && enabledProtocols.length > 0) {
            engine.setEnabledProtocols(enabledProtocols);
        }
        return engine;

    }

    @Override
    public void onApplicationEvent(SystemEvent event) {

        if (event instanceof WebappCreatedEvent) {
            sessionCookieName = getApplicationName().toUpperCase().replace(' ', '_') + "_HTTP_SESSION";
        }
        if (event.getResourceKey().equals(ConfigurationChangedEvent.EVENT_RESOURCE_KEY)) {
            String resourceKey = (String) event.getAttribute(ConfigurationChangedEvent.ATTR_CONFIG_RESOURCE_KEY);
            if (resourceKey.equals("ssl.ciphers") || resourceKey.equals("ssl.protocols")) {
                rebuildEnabledCipherSuites();
            }
        }
        if (event instanceof ConfigurationChangedEvent) {
            ConfigurationChangedEvent configEvent = (ConfigurationChangedEvent) event;
            if (configEvent.getResourceKey().equals("application.path")) {
                System.setProperty("hypersocket.appPath", getBasePath());
            } else if (configEvent.getResourceKey().equals("ui.path")) {
                System.setProperty("hypersocket.uiPath", getUiPath());
            }

        }
    }

    @Override
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public String getUserInterfacePath() {
        return configurationService.getValue("ui.path");
    }

    @Override
    public void addCompressablePath(String path) {
        compressablePaths.add(path);
    }

    @Override
    public boolean isCompressablePath(String uri) {
        for (String path : compressablePaths) {
            if (uri.startsWith(path)) {
                return true;
            }
        }
        return false;
    }

}