/*
*
* 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.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();
for(File chart:mapDir.listFiles()){
if(chart.isDirectory()){
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;
}
}