nz.co.fortytwo.signalk.server.SignalKServer.java Source code

Java tutorial

Introduction

Here is the source code for nz.co.fortytwo.signalk.server.SignalKServer.java

Source

/*
 * 
 * Copyright (C) 2012-2014 R T Huitema. All Rights Reserved.
 * Web: www.42.co.nz
 * Email: robert@42.co.nz
 * Author: R T Huitema
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * 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 nz.co.fortytwo.signalk.server;

import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_DISCOVERY;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import javax.jmdns.JmmDNS;
import javax.jmdns.ServiceInfo;

import org.apache.activemq.broker.BrokerService;
import org.apache.camel.CamelContext;
import org.apache.camel.Route;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.impl.PropertyPlaceholderDelegateRegistry;
import org.apache.camel.main.Main;
import org.apache.camel.main.MainSupport;
import org.apache.camel.model.RouteDefinition;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.webapp.WebAppContext;

import nz.co.fortytwo.signalk.model.impl.SignalKModelFactory;
import nz.co.fortytwo.signalk.util.ConfigConstants;
import nz.co.fortytwo.signalk.util.SignalKConstants;
import nz.co.fortytwo.signalk.util.Util;

public class SignalKServer {

    private static Server server;

    private static Logger logger = LogManager.getLogger(SignalKServer.class);

    private JmmDNS jmdns = null;

    protected SignalKServer(String configDir) throws Exception {
        // init config
        Properties props = System.getProperties();
        props.setProperty("java.net.preferIPv4Stack", "true");
        System.setProperties(props);

        Util.getConfig();
        // make sure we have all the correct dirs and files now
        ensureInstall();

        logger.info("SignalKServer starting....");

        // do we have a USB drive connected?
        //logger.info("USB drive " + Util.getUSBFile());

        // create a new Camel Main so we can easily start Camel
        Main main = new Main();
        //main.setApplicationContextUri("classpath:META-INF/spring/camel-context.xml");
        // enable hangup support which mean we detect when the JVM terminates,
        // and stop Camel graceful
        main.enableHangupSupport();

        // Start activemq broker
        BrokerService broker = ActiveMqBrokerFactory.newInstance();

        broker.start();
        //DNS-SD, zeroconf mDNS
        startMdns();
        configureRouteManager(main);
        // and run, which keeps blocking until we terminate the JVM (or stop
        // CamelContext)
        main.start();

        WatchService service = FileSystems.getDefault().newWatchService();
        Path dir = Paths.get("./conf");
        dir.register(service, StandardWatchEventKinds.ENTRY_MODIFY);
        WatchKey key = null;
        while (true) {
            key = service.take();
            // Dequeueing events
            Kind<?> kind = null;
            for (WatchEvent<?> watchEvent : key.pollEvents()) {
                // Get the type of the event
                kind = watchEvent.kind();
                logger.debug(
                        "SignalKServer conf/ event:" + watchEvent.kind() + " : " + watchEvent.context().toString());
                if (StandardWatchEventKinds.OVERFLOW == kind) {
                    continue; //loop
                } else if (StandardWatchEventKinds.ENTRY_MODIFY == kind) {
                    // A new Path was created 
                    @SuppressWarnings("unchecked")
                    Path newPath = ((WatchEvent<Path>) watchEvent).context();
                    // Output
                    if (newPath.endsWith("signalk-restart")) {
                        logger.info("SignalKServer conf/signalk-restart changed, stopping..");
                        main.stop();
                        main.getCamelContexts().clear();
                        main.getRouteBuilders().clear();
                        main.getRouteDefinitions().clear();

                        // so now shutdown serial reader and server
                        RouteManager routeManager = RouteManagerFactory.getInstance();
                        routeManager.stopNettyServers();
                        routeManager.stopSerial();
                        if (server != null) {
                            server.stop();
                            server = null;
                        }
                        RouteManagerFactory.clear();
                        configureRouteManager(main);
                        main.start();
                    }

                }
            }

            if (!key.reset()) {
                break; //loop
            }
        }

        stopMdns();
        broker.stop();
        // write out the signalk model
        SignalKModelFactory.save(SignalKModelFactory.getInstance());
        System.exit(0);
    }

    private void configureRouteManager(MainSupport main) throws Exception {
        logger.info("SignalKServer conf/signalk-config.json changed, restarting");

        if (Util.getConfigPropertyBoolean(ConfigConstants.HAWTIO_START)) {
            logger.info("SignalKServer starting hawtio manager....");
            server = startHawtio();
        } else {
            //start jolokia for remote management
            logger.info("SignalKServer starting jolokia remote management agent....");
            server = startJolokia();
        }

        RouteManager routeManager = RouteManagerFactory.getInstance();

        // add our routes to Camel
        main.addRouteBuilder(routeManager);

        logger.info("SignalKServer configured");

    }

    private Server startJolokia() throws Exception {
        Properties props = System.getProperties();
        props.setProperty("jolokia.authenticationEnabled", "false");
        System.setProperties(props);
        //System.setProperty("hawtio.authenticationEnabled",Util.getConfigPropertyBoolean(ConfigConstants.HAWTIO_AUTHENTICATE).toString());
        int hawtPort = Util.getConfigPropertyInt(ConfigConstants.JOLOKIA_PORT);
        return startServer(hawtPort, Util.getConfigProperty(ConfigConstants.JOLOKIA_CONTEXT),
                Util.getConfigProperty(ConfigConstants.JOLOKIA_WAR), "/.jolokia");
    }

    private Server startHawtio() throws Exception {
        // hawtio, auth disabled
        Properties props = System.getProperties();
        props.setProperty("hawtio.authenticationEnabled", "false");
        System.setProperties(props);
        //System.setProperty("hawtio.authenticationEnabled",Util.getConfigPropertyBoolean(ConfigConstants.HAWTIO_AUTHENTICATE).toString());
        int hawtPort = Util.getConfigPropertyInt(ConfigConstants.HAWTIO_PORT);
        return startServer(hawtPort, Util.getConfigProperty(ConfigConstants.HAWTIO_CONTEXT),
                Util.getConfigProperty(ConfigConstants.HAWTIO_WAR), "/.hawtio");
    }

    private Server startServer(int hawtPort, String contextPath, String war, String dirName) throws Exception {

        Server server = new Server(hawtPort);
        HandlerCollection handlers = new HandlerCollection();
        handlers.setServer(server);
        server.setHandler(handlers);
        WebAppContext webapp = new WebAppContext();
        webapp.setServer(server);
        webapp.setContextPath(contextPath);

        webapp.setWar(war);
        webapp.setParentLoaderPriority(true);
        webapp.setLogUrlOnStart(true);
        // lets set a temporary directory so jetty doesn't bork if some process
        // zaps /tmp/*
        String homeDir = System.getProperty("user.home", ".") + dirName;
        String tempDirPath = homeDir + "/tmp";
        File tempDir = new File(tempDirPath);
        tempDir.mkdirs();
        logger.info("using temp directory for hawtio/jolokia jetty: " + tempDir.getPath());
        webapp.setTempDirectory(tempDir);
        // add hawtio
        handlers.addHandler(webapp);

        server.start();
        return server;
    }

    private void ensureInstall() throws IOException {

        File rootDir = new File(".");
        //if (Util.cfg != null) {
        //   rootDir = Util.cfg.getParentFile().getParentFile();
        //}
        // do we have a log dir?
        File logDir = new File(rootDir, "logs");
        if (!logDir.exists()) {
            logDir.mkdirs();
        }
        // do we have a log4j.properties?
        File log4j = new File(rootDir, "conf/log4j2.json");
        if (!log4j.exists()) {
            File log4jSample = new File(rootDir, "conf/log4j2.json.sample");
            FileUtils.copyFile(log4jSample, log4j);
        }
        Configurator.initialize("signalk", log4j.toString());
        // do we have a storage dir?
        File storageDir = new File(Util.getConfigProperty(ConfigConstants.STORAGE_ROOT));
        if (!storageDir.exists()) {
            storageDir.mkdirs();
        }

    }

    /**
     * Stop the DNS-SD server.
     * @throws IOException 
     */
    public void stopMdns() throws IOException {
        if (jmdns != null) {
            jmdns.unregisterAllServices();
            jmdns.close();
            jmdns = null;
        }
    }

    private void startMdns() {
        //DNS-SD
        //NetworkTopologyDiscovery netTop = NetworkTopologyDiscovery.Factory.getInstance();
        Runnable r = new Runnable() {

            @Override
            public void run() {
                jmdns = JmmDNS.Factory.getInstance();

                jmdns.registerServiceType(SignalKConstants._SIGNALK_WS_TCP_LOCAL);
                jmdns.registerServiceType(SignalKConstants._SIGNALK_HTTP_TCP_LOCAL);
                ServiceInfo wsInfo = ServiceInfo.create(SignalKConstants._SIGNALK_WS_TCP_LOCAL, "signalk-ws",
                        Util.getConfigPropertyInt(ConfigConstants.WEBSOCKET_PORT), 0, 0, getMdnsTxt());
                try {
                    jmdns.registerService(wsInfo);
                    ServiceInfo httpInfo = ServiceInfo.create(SignalKConstants._SIGNALK_HTTP_TCP_LOCAL,
                            "signalk-http", Util.getConfigPropertyInt(ConfigConstants.REST_PORT), 0, 0,
                            getMdnsTxt());
                    jmdns.registerService(httpInfo);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t = new Thread(r);
        t.setDaemon(true);
        t.start();

    }

    private Map<String, String> getMdnsTxt() {
        Map<String, String> txtSet = new HashMap<String, String>();
        txtSet.put("path", SIGNALK_DISCOVERY);
        txtSet.put("server", "signalk-server");
        txtSet.put("version", Util.getConfigProperty(ConfigConstants.VERSION));
        txtSet.put("vessel_name", Util.getConfigProperty(ConfigConstants.UUID));
        txtSet.put("vessel_mmsi", Util.getConfigProperty(ConfigConstants.UUID));
        txtSet.put("vessel_uuid", Util.getConfigProperty(ConfigConstants.UUID));
        return txtSet;
    }

    public static void main(String[] args) throws Exception {
        // we look for and use a freeboard.cfg in the launch/cfg dir and use
        // that to override defaults
        // the only arg is conf dir
        String conf = null;
        if (args != null && args.length > 0 && StringUtils.isNotBlank(args[0])) {
            conf = args[0];
            if (!conf.endsWith("/")) {
                conf = conf + "/";
            }
        }
        //Configurator.initialize("test","./conf/log4j.json");
        SignalKServerFactory.getInstance(conf);

    }

}