/*
*
* 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);
}
}