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

Java tutorial

Introduction

Here is the source code for nz.co.fortytwo.signalk.server.RouteManager.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.ConfigConstants.MAP_DIR;
import static nz.co.fortytwo.signalk.util.ConfigConstants.OUTPUT_XMPP;
import static nz.co.fortytwo.signalk.util.ConfigConstants.STATIC_DIR;
import static nz.co.fortytwo.signalk.util.SignalKConstants.DEMO;
//import static nz.co.fortytwo.signalk.util.ConfigConstants.UUID;
import static nz.co.fortytwo.signalk.util.SignalKConstants.FORMAT_DELTA;
import static nz.co.fortytwo.signalk.util.SignalKConstants.MSG_SRC_BUS;
import static nz.co.fortytwo.signalk.util.SignalKConstants.MSG_SRC_IP;
import static nz.co.fortytwo.signalk.util.SignalKConstants.MSG_TYPE;
import static nz.co.fortytwo.signalk.util.SignalKConstants.POLICY_IDEAL;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_API;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_AUTH;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_CONFIG;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_DISCOVERY;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_INSTALL;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_LOGGER;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_RESTART;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_UPGRADE;
import static nz.co.fortytwo.signalk.util.SignalKConstants.SIGNALK_UPLOAD;
import static nz.co.fortytwo.signalk.util.SignalKConstants._SIGNALK_WS_TCP_LOCAL;
import static nz.co.fortytwo.signalk.util.SignalKConstants.dot;

import java.io.File;
import java.net.Inet4Address;
import java.util.Arrays;
import java.util.TreeMap;
import java.util.UUID;

import javax.jmdns.JmmDNS;
import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceListener;

import org.apache.camel.Endpoint;
import org.apache.camel.ExchangePattern;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.ahc.ws.WsEndpoint;
import org.apache.camel.component.stomp.SkStompComponent;
import org.apache.camel.component.websocket.SignalkWebsocketComponent;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.impl.PropertyPlaceholderDelegateRegistry;
import org.apache.camel.model.RouteDefinition;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.security.Constraint;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smackx.jiveproperties.JivePropertiesManager;

import mjson.Json;
import nz.co.fortytwo.signalk.model.SignalKModel;
import nz.co.fortytwo.signalk.model.impl.SignalKModelFactory;
import nz.co.fortytwo.signalk.processor.UploadProcessor;
import nz.co.fortytwo.signalk.util.ConfigConstants;
import nz.co.fortytwo.signalk.util.Util;

/**
 * Main camel route definition to handle input to signalk
 * 
 * 
 * <ul>
 * <li>Basically all input is added to seda:input
 * <li>Message is converted to hashmap, processed,added to signalk model
 * <li>Output is sent out 1 sec.
 * </ul>
 * 
 * 
 * @author robert
 * 
 */
public class RouteManager extends RouteBuilder {
    protected static final String JETTY_HTTP_0_0_0_0 = "jetty:http://0.0.0.0:";

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

    //public static final String SEDA_INPUT = "seda:inputData?purgeWhenStopping=true&size=1000";
    public static final String SEDA_INPUT = "activemq:queue:signalk.inputData?jmsMessageType=Text&timeToLive=10000&asyncConsumer=true&acceptMessagesWhileStopping=true";
    public static final String SEDA_XMPP = "activemq:queue:signalk.xmppData?jmsMessageType=Text&timeToLive=10000&asyncConsumer=true&acceptMessagesWhileStopping=true";
    public static final String SEDA_WEBSOCKETS = "seda:websockets?purgeWhenStopping=true&size=1000";
    public static final String DIRECT_STOMP = "direct:stomp";
    public static final String DIRECT_MQTT = "direct:mqtt";
    public static final String DIRECT_XMPP = "direct:xmpp";
    public static final String DIRECT_TCP = "seda:tcp?purgeWhenStopping=true&size=1000";

    public static final String SEDA_NMEA = "seda:nmeaOutput?purgeWhenStopping=true&size=100";
    public static final String SEDA_COMMON_OUT = "seda:commonOut?purgeWhenStopping=true&size=100";

    public static final String STOMP = "skStomp:queue:signalk?brokerURL=tcp://0.0.0.0:"
            + Util.getConfigPropertyInt(ConfigConstants.STOMP_PORT);
    public static final String MQTT = "mqtt:signalk?host=tcp://0.0.0.0:"
            + Util.getConfigPropertyInt(ConfigConstants.MQTT_PORT);

    private int wsPort = 3000;
    private int restPort = 8080;
    //private String streamUrl;

    private SerialPortManager serialPortManager;

    private SignalKModel signalkModel = SignalKModelFactory.getInstance();

    private NettyServer skServer;
    private NettyServer nmeaServer;

    protected RouteManager() {

        // web socket on port 3000
        logger.info("  Websocket port:" + Util.getConfigPropertyInt(ConfigConstants.WEBSOCKET_PORT));
        wsPort = Util.getConfigPropertyInt(ConfigConstants.WEBSOCKET_PORT);
        logger.info("  Signalk REST API port:" + Util.getConfigPropertyInt(ConfigConstants.REST_PORT));
        restPort = Util.getConfigPropertyInt(ConfigConstants.REST_PORT);

    }

    @Override
    public void configure() throws Exception {
        configure0();
    }

    public void configure0() throws Exception {
        //XMPP
        JivePropertiesManager.setJavaObjectEnabled(true);
        SASLAuthentication.unsupportSASLMechanism("DIGEST-MD5");
        SASLAuthentication.unregisterSASLMechanism("DIGEST-MD5");
        SASLAuthentication.supportSASLMechanism("PLAIN", 0);
        //SmackConfiguration.DEBUG_ENABLED=true;

        errorHandler(
                deadLetterChannel("direct:fail").useOriginalMessage().maximumRedeliveries(1).redeliveryDelay(1000));

        from("direct:fail").id("Fail").to("log:nz.co.fortytwo.signalk.error?level=ERROR&showAll=true");

        SignalKModelFactory.load(signalkModel);

        //set shutdown quickly, 5 min is too long
        CamelContextFactory.getInstance().getShutdownStrategy().setShutdownNowOnTimeout(true);
        CamelContextFactory.getInstance().getShutdownStrategy().setTimeout(10);
        //CamelContextFactory.getInstance().addComponent("activemq", ActiveMQComponent.activeMQComponent("vm://localhost?broker.persistent=false"));

        //Netty tcp server
        skServer = new NettyServer(null, ConfigConstants.OUTPUT_TCP);
        skServer.setTcpPort(Util.getConfigPropertyInt(ConfigConstants.TCP_PORT));
        skServer.setUdpPort(Util.getConfigPropertyInt(ConfigConstants.UDP_PORT));
        skServer.run();

        nmeaServer = new NettyServer(null, ConfigConstants.OUTPUT_NMEA);
        nmeaServer.setTcpPort(Util.getConfigPropertyInt(ConfigConstants.TCP_NMEA_PORT));
        nmeaServer.setUdpPort(Util.getConfigPropertyInt(ConfigConstants.UDP_NMEA_PORT));
        nmeaServer.run();

        // start a serial port manager
        if (serialPortManager == null) {
            serialPortManager = new SerialPortManager();
        }
        new Thread(serialPortManager).start();

        // main input to destination route
        // put all input into signalk model 
        SignalkRouteFactory.configureInputRoute(this, SEDA_INPUT);

        File htmlRoot = new File(Util.getConfigProperty(ConfigConstants.STATIC_DIR));
        log.info("Serving static files from " + htmlRoot.getAbsolutePath());

        //restlet

        //bind in registry
        PropertyPlaceholderDelegateRegistry registry = (PropertyPlaceholderDelegateRegistry) CamelContextFactory
                .getInstance().getRegistry();
        JndiRegistry reg = (JndiRegistry) registry.getRegistry();
        if (reg.lookup("staticHandler") == null) {

            ResourceHandler staticHandler = new ResourceHandler();
            staticHandler.setResourceBase(Util.getConfigProperty(ConfigConstants.STATIC_DIR));
            staticHandler.setDirectoriesListed(false);
            MimeTypes mimeTypes = staticHandler.getMimeTypes();
            mimeTypes.addMimeMapping("log", MimeTypes.TEXT_HTML_UTF_8);
            staticHandler.setMimeTypes(mimeTypes);
            //static files
            reg.bind("staticHandler", staticHandler);

        }
        if (reg.lookup("staticConfigHandler") == null) {

            Constraint constraint = new Constraint("BASIC", "rolename");
            constraint.setAuthenticate(true);

            ConstraintMapping mapping = new ConstraintMapping();
            mapping.setPathSpec("/config/*");
            mapping.setConstraint(constraint);

            ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
            securityHandler.setAuthenticator(new BasicAuthenticator());
            securityHandler.setLoginService(new SignalkLoginService());
            securityHandler.addConstraintMapping(mapping);

            ResourceHandler staticHandler = new ResourceHandler();
            staticHandler.setResourceBase(Util.getConfigProperty(ConfigConstants.STATIC_DIR) + "config/");
            staticHandler.setDirectoriesListed(false);
            MimeTypes mimeTypes = staticHandler.getMimeTypes();
            mimeTypes.addMimeMapping("log", MimeTypes.TEXT_HTML_UTF_8);
            staticHandler.setMimeTypes(mimeTypes);
            securityHandler.setHandler(staticHandler);
            //static files
            reg.bind("staticConfigHandler", securityHandler);

        }

        if (reg.lookup("securityHandler") == null) {
            Constraint constraint = new Constraint("BASIC", "rolename");
            constraint.setAuthenticate(true);

            ConstraintMapping configMapping = new ConstraintMapping();
            configMapping.setPathSpec("/signalk/v1/config/*");
            configMapping.setConstraint(constraint);

            ConstraintMapping installMapping = new ConstraintMapping();
            installMapping.setPathSpec("/signalk/v1/install/*");
            installMapping.setConstraint(constraint);

            ConstraintMapping upgradeMapping = new ConstraintMapping();
            upgradeMapping.setPathSpec("/signalk/v1/upgrade/*");
            upgradeMapping.setConstraint(constraint);

            ConstraintMapping restartMapping = new ConstraintMapping();
            restartMapping.setPathSpec("/signalk/v1/restart");
            restartMapping.setConstraint(constraint);

            ConstraintMapping uploadMapping = new ConstraintMapping();
            uploadMapping.setPathSpec("/signalk/v1/upload/*");
            uploadMapping.setConstraint(constraint);

            ConstraintMapping loggerMapping = new ConstraintMapping();
            loggerMapping.setPathSpec("/signalk/v1/logger/*");
            loggerMapping.setConstraint(constraint);

            ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
            securityHandler.setAuthenticator(new BasicAuthenticator());
            securityHandler.setLoginService(new SignalkLoginService());
            securityHandler.addConstraintMapping(installMapping);
            securityHandler.addConstraintMapping(upgradeMapping);
            securityHandler.addConstraintMapping(restartMapping);
            securityHandler.addConstraintMapping(loggerMapping);
            securityHandler.addConstraintMapping(configMapping);
            securityHandler.addConstraintMapping(uploadMapping);

            reg.bind("securityHandler", securityHandler);
        }

        restConfiguration().component("jetty").consumerProperty("matchOnUriPrefix", "true")
                .componentProperty("matchOnUriPrefix", "true").host("0.0.0.0").port(restPort);

        //websockets

        if (CamelContextFactory.getInstance().getComponent("skWebsocket") == null) {
            SignalkWebsocketComponent skws = new SignalkWebsocketComponent();
            CamelContextFactory.getInstance().addComponent("skWebsocket", skws);
        }
        //STOMP
        if (CamelContextFactory.getInstance().getComponent("skStomp") == null) {
            CamelContextFactory.getInstance().addComponent("skStomp", new SkStompComponent());
        }

        //setup routes
        SignalkRouteFactory.configureWebsocketTxRoute(this, SEDA_WEBSOCKETS, wsPort);
        SignalkRouteFactory.configureWebsocketRxRoute(this, SEDA_INPUT, wsPort);

        SignalkRouteFactory.configureTcpServerRoute(this, DIRECT_TCP, skServer, ConfigConstants.OUTPUT_TCP);
        SignalkRouteFactory.configureTcpServerRoute(this, SEDA_NMEA, nmeaServer, ConfigConstants.OUTPUT_NMEA);

        SignalkRouteFactory.configureCommonOut(this);

        SignalkRouteFactory.configureHeartbeatRoute(this, "timer://heartbeat?fixedRate=true&period=1000");

        SignalkRouteFactory.configureAuthRoute(this, JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_AUTH
                + "?sessionSupport=true&matchOnUriPrefix=true&handlers=#securityHandler,#staticHandler,#staticConfigHandler&enableJMX=true&enableCORS=true");
        SignalkRouteFactory.configureRestRoute(this,
                JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_DISCOVERY
                        + "?sessionSupport=true&matchOnUriPrefix=false&enableJMX=true&enableCORS=true",
                "REST Discovery");
        SignalkRouteFactory.configureRestRoute(this, JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_API
                + "?sessionSupport=true&matchOnUriPrefix=true&enableJMX=true&enableCORS=true", "REST Api");
        SignalkRouteFactory.configureRestConfigRoute(this,
                JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_CONFIG
                        + "?sessionSupport=true&matchOnUriPrefix=true&enableJMX=true&enableCORS=false",
                "Config Api");

        SignalkRouteFactory.configureRestLoggerRoute(this, JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_LOGGER
                + "?sessionSupport=true&matchOnUriPrefix=true&enableJMX=true&enableCORS=false", "Logger");

        SignalkRouteFactory.configureRestUploadRoute(this, SIGNALK_UPLOAD, "Upload");

        if (Util.getConfigPropertyBoolean(ConfigConstants.ALLOW_INSTALL)) {
            SignalkRouteFactory.configureInstallRoute(this, JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_INSTALL
                    + "?sessionSupport=true&matchOnUriPrefix=true&enableJMX=true", "REST Install");
        }

        if (Util.getConfigPropertyBoolean(ConfigConstants.ALLOW_UPGRADE)) {
            SignalkRouteFactory.configureInstallRoute(this, JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_UPGRADE
                    + "?sessionSupport=true&matchOnUriPrefix=true&enableJMX=true", "REST Upgrade");
        }

        // timed actions
        SignalkRouteFactory.configureBackgroundTimer(this, "timer://background?fixedRate=true&period=60000");
        SignalkRouteFactory.configureWindTimer(this, "timer://wind?fixedRate=true&period=1000");
        SignalkRouteFactory.configureDepthTimer(this, "timer://depth?fixedRate=true&period=1000");
        SignalkRouteFactory.configureAnchorWatchTimer(this, "timer://anchorWatch?fixedRate=true&period=5000");
        SignalkRouteFactory.configureAlarmsTimer(this, "timer://alarms?fixedRate=true&period=1000");

        if (Util.getConfigPropertyBoolean(ConfigConstants.GENERATE_NMEA0183)) {
            SignalkRouteFactory.configureNMEA0183Timer(this, "timer://nmea0183?fixedRate=true&period=1000");
        }
        //STOMP
        if (Util.getConfigPropertyBoolean(ConfigConstants.START_STOMP)) {
            from("skStomp:queue:signalk.put").id("STOMP In")
                    .setHeader(ConfigConstants.OUTPUT_TYPE, constant(ConfigConstants.OUTPUT_STOMP))
                    .setHeader(MSG_SRC_BUS, constant("stomp.queue:signalk.put")).to(SEDA_INPUT)
                    .id(SignalkRouteFactory.getName("SEDA_INPUT"));
        }
        //MQTT
        if (Util.getConfigPropertyBoolean(ConfigConstants.START_MQTT)) {
            from(MQTT + "&subscribeTopicName=signalk.put").id("MQTT In").transform(body().convertToString())
                    .setHeader(ConfigConstants.OUTPUT_TYPE, constant(ConfigConstants.OUTPUT_MQTT))
                    .setHeader(MSG_SRC_BUS, constant("mqtt.queue:signalk.put")).to(SEDA_INPUT)
                    .id(SignalkRouteFactory.getName("SEDA_INPUT"));
        }
        //start any clients if they exist
        //WS
        Json wsClients = Util.getConfigJsonArray(ConfigConstants.CLIENT_WS);
        logger.info("  WS client connections : " + wsClients);
        if (wsClients != null) {
            for (Object client : wsClients.asList()) {
                logger.info("  Starting WS client connection to url:ahc-ws://" + client);
                WsEndpoint wsEndpoint = (WsEndpoint) getContext().getEndpoint("ahc-ws://" + client);
                setupClient("ahc-ws://" + client, client.toString(), "ws");
                wsEndpoint.connect();
            }
        }
        //TCP
        Json tcpClients = Util.getConfigJsonArray(ConfigConstants.CLIENT_TCP);
        if (tcpClients != null) {
            for (Object client : tcpClients.asList()) {
                setupClient("netty4:tcp://" + client + "?clientMode=true&textline=true", client.toString(), "tcp");
            }
        }
        //MQTT
        Json mqttClients = Util.getConfigJsonArray(ConfigConstants.CLIENT_MQTT);
        if (mqttClients != null) {
            for (Object client : mqttClients.asList()) {
                setupClient("mqtt://" + client, client.toString(), "mqtt");
            }
        }

        //STOMP
        //TODO: test stomp client actually works!
        Json stompClients = Util.getConfigJsonArray(ConfigConstants.CLIENT_STOMP);
        if (stompClients != null) {
            for (Object client : stompClients.asList()) {
                setupClient("stomp://" + client, client.toString(), "stomp");
            }
        }

        //XMPP
        //"xmpp": [{"server":"xmpp.www.42.co.nz","passwd":"motu","user":"motu","room":"signalk"}]
        Json xmppClients = Util.getConfigJsonArray(ConfigConstants.XMPP);
        if (xmppClients != null) {
            for (Json client : xmppClients.asJsonList()) {
                String server = client.at("server").asString();
                String user = client.at("user").asString();
                String passwd = client.at("passwd").asString();
                String room = client.at("room").asString();
                String filter = client.at("filter").asString();
                Endpoint xmppEndpoint = getContext()
                        .getEndpoint("xmpp://" + server + "?testConnectionOnStartup=false&room=" + room + "&user="
                                + user + "&password=" + passwd + "&resource=" + user + "&serviceName=" + server);
                //receive
                setupClient(xmppEndpoint, server + dot + room, "xmpp");
                //tx
                from(SEDA_XMPP + "&selector=" + ConfigConstants.DESTINATION + " %3D '" + room + "'")
                        .id("XMPP out: " + room).convertBodyTo(String.class).to(xmppEndpoint)
                        .id("XMPP Service:" + room);
                //and subscribe
                String wsSession = UUID.randomUUID().toString();
                SubscriptionManagerFactory.getInstance().add(wsSession, wsSession, OUTPUT_XMPP,
                        Inet4Address.getLocalHost().getHostAddress(),
                        Inet4Address.getByName(server).getHostAddress());
                for (String f : filter.split(",")) {
                    Subscription sub = new Subscription(wsSession, f, 5000, 1000, FORMAT_DELTA, POLICY_IDEAL);
                    sub.setDestination(room);
                    SubscriptionManagerFactory.getInstance().addSubscription(sub);
                }
            }
        }
        //reload charts into resources
        reloadCharts();

        //restart support
        from(JETTY_HTTP_0_0_0_0 + restPort + SIGNALK_RESTART).id("Restart route")
                .setExchangePattern(ExchangePattern.InOut).setBody(constant("Restarting now.."))
                .to("file://./conf/?fileName=signalk-restart");

        //Demo mode
        if (Util.getConfigPropertyBoolean(ConfigConstants.DEMO)) {
            String streamUrl = Util.getConfigProperty(ConfigConstants.STREAM_URL);
            logger.info("  Demo streaming url:" + Util.getConfigProperty(ConfigConstants.STREAM_URL));
            from("file://./src/test/resources/samples/?move=done&fileName=" + streamUrl).id("demo feed")
                    .onException(Exception.class).handled(true).maximumRedeliveries(0)
                    .to("log:nz.co.fortytwo.signalk.model.receive?level=ERROR&showException=true&showStackTrace=true")
                    .end().split(body().regexTokenize("[\r\n|\n|\r]")).streaming().convertBodyTo(String.class)
                    .throttle(4).timePeriodMillis(1000).setHeader(MSG_TYPE, constant(DEMO))
                    //.setHeader(MSG_SRC_IP,constant("127.0.0.1"))
                    .setHeader(MSG_SRC_BUS, constant("demo")).to(SEDA_INPUT)
                    .id(SignalkRouteFactory.getName("SEDA_INPUT")).end();

            //and copy it back again to rerun it
            from("file://./src/test/resources/samples/done?fileName=" + streamUrl).id("demo restart")
                    .onException(Exception.class).handled(true).maximumRedeliveries(0).end()
                    .to("file://./src/test/resources/samples/?fileName=" + streamUrl);
        }

        SignalkRouteFactory.startLogRoutes(this, JETTY_HTTP_0_0_0_0, restPort);

        if (Util.getConfigPropertyBoolean(ConfigConstants.ZEROCONF_AUTO)) {
            startMdnsAutoconnect();
        }
    }

    private void reloadCharts() {
        String staticDir = Util.getConfigProperty(STATIC_DIR);
        if (!staticDir.endsWith("/")) {
            staticDir = staticDir + "/";
        }
        File mapDir = new File(staticDir + Util.getConfigProperty(MAP_DIR));
        logger.debug("Reloading charts from: " + mapDir.getAbsolutePath());
        if (mapDir == null || !mapDir.exists() || mapDir.listFiles() == null)
            return;
        UploadProcessor processor = new UploadProcessor();
        TreeMap<String, Object> treeMap = new TreeMap<String, Object>(signalkModel.getSubMap("resources.charts"));
        for (File chart : mapDir.listFiles()) {
            if (chart.isDirectory()) {
                if (treeMap.containsValue(chart.getName())) {
                    logger.info("chart " + chart.getName() + " already in model");
                } else {
                    logger.debug("Reloading: " + chart.getName());
                    try {
                        processor.loadChart(chart.getName());
                    } catch (Exception e) {
                        logger.warn(e.getMessage());
                    }
                }
            }
        }

    }

    private void setupClient(String endpoint, String client, String serviceName) {
        setupClient(getContext().getEndpoint(endpoint), client, serviceName);
    }

    private void setupClient(Endpoint endpoint, String client, String serviceName) {
        from(endpoint).id(serviceName.toUpperCase() + " Client:" + client).onException(Exception.class)
                .handled(true).maximumRedeliveries(0)
                .to("log:nz.co.fortytwo.signalk.client." + serviceName
                        + "?level=ERROR&showException=true&showStackTrace=true")
                .end().to("log:nz.co.fortytwo.signalk.client." + serviceName + "?level=DEBUG")
                .convertBodyTo(String.class)
                .setHeader(MSG_SRC_BUS, constant(serviceName + "." + client.toString().replace('.', '_')))
                .to(SEDA_INPUT);
    }

    private void startMdnsAutoconnect() {
        //now listen and report other services
        logger.info("Starting jmdns listener..");
        JmmDNS.Factory.getInstance().addServiceListener(_SIGNALK_WS_TCP_LOCAL, new ServiceListener() {

            @Override
            public void serviceResolved(ServiceEvent evt) {
                try {
                    String name = evt.getName();
                    String thisHost = evt.getDNS().getInetAddress().getHostAddress();
                    logger.info("Resolved mDns service:" + name + " at " + thisHost);
                    logger.debug(name + " Server:" + evt.getInfo().getServer());
                    String[] remoteHost = evt.getInfo().getHostAddresses();
                    logger.debug(name + " Remotehost:" + remoteHost[0]);

                    logger.debug(name + " URLs:" + Arrays.toString(evt.getInfo().getURLs()));
                    if (thisHost.startsWith(remoteHost[0]) || evt.getDNS().getInetAddress().isLinkLocalAddress()
                            || evt.getDNS().getInetAddress().isLoopbackAddress()) {
                        logger.info(name + " Found own host: " + remoteHost[0] + ", ignoring..");
                        return;
                    }
                    if (remoteHost[0].startsWith("[fe80") || evt.getDNS().getInetAddress().isLinkLocalAddress()) {
                        logger.info(name + " Found ipv6 host: " + remoteHost[0] + ", ignoring..");
                        return;
                    }
                    //we want to connect here
                    String url = evt.getInfo().getURLs()[0];
                    if (StringUtils.isNotBlank(url)) {
                        logger.info(name + " Connecting to: " + url);

                        url = url.substring(url.indexOf("://") + 3);
                        url = url + "/v1/stream";
                        logger.info("  Starting WS connection to url:ahc-ws://" + url);

                        startWsClient(url);

                    }

                } catch (Exception e) {

                    logger.error(e);
                }

            }

            @Override
            public void serviceRemoved(ServiceEvent evt) {
                logger.info("Lost mDns service:" + evt.getName());
            }

            @Override
            public void serviceAdded(ServiceEvent evt) {
                logger.info("Found mDns service:" + evt.getName() + " at " + evt.getType());

            }
        });
        logger.info("Started jmdns listener");

    }

    private void startWsClient(String client) throws Exception {
        WsEndpoint wsEndpoint = (WsEndpoint) getContext().getEndpoint("ahc-ws://" + client);
        RouteDefinition route = from(wsEndpoint);

        route.onException(Exception.class).handled(true).maximumRedeliveries(0)
                .to("log:nz.co.fortytwo.signalk.client.ws?level=ERROR&showException=true&showStackTrace=true").end()
                .to("log:nz.co.fortytwo.signalk.client.ws?level=DEBUG").convertBodyTo(String.class)
                .setHeader(MSG_SRC_BUS, constant("ws." + client.toString().replace('.', '_'))).to(SEDA_INPUT);
        route.setId("Websocket Client:" + client);
        ((DefaultCamelContext) CamelContextFactory.getInstance()).addRouteDefinition(route);
        ((DefaultCamelContext) CamelContextFactory.getInstance()).startRoute(route.getId());
        wsEndpoint.connect();

    }

    public void stopNettyServers() {
        if (skServer != null) {
            skServer.shutdownServer();
            skServer = null;
        }

        if (nmeaServer != null) {
            nmeaServer.shutdownServer();
            nmeaServer = null;
        }
    }

    /**
     * When the serial port is used to read from the arduino this must be called to shut
     * down the readers, which are in their own threads.
     */
    public void stopSerial() {
        serialPortManager.stopSerial();
        serialPortManager = null;
    }

}