/** * Copyright (c) 2011-2014, OpenIoT * * This file is part of OpenIoT. * * OpenIoT is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * OpenIoT is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with OpenIoT. If not, see <http://www.gnu.org/licenses/>. * * Contact: OpenIoT mailto: info@openiot.eu * @author parobert * @author cl3m * @author Jerome Rousselot * @author gsn_devs * @author Mehdi Riahi * @author Ali Salehi * @author Behnaz Bostanipour * @author Timotee Maret * @author Julien Eberle */ package org.openiot.gsn; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.RenderingHints; import java.awt.SplashScreen; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.CertificateException; import java.util.HashMap; import java.util.Properties; import java.util.Random; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; import org.eclipse.jetty.http.security.Constraint; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; import org.eclipse.jetty.security.authentication.BasicAuthenticator; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.session.HashSessionIdManager; import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.webapp.WebAppContext; import org.jibx.runtime.BindingDirectory; import org.jibx.runtime.IBindingFactory; import org.jibx.runtime.IUnmarshallingContext; import org.jibx.runtime.JiBXException; import org.openiot.gsn.beans.ContainerConfig; import org.openiot.gsn.beans.StorageConfig; import org.openiot.gsn.beans.VSensorConfig; import org.openiot.gsn.dynamicSensorControl.DynamicControlTaskTimer; import org.openiot.gsn.http.ac.ConnectToDB; import org.openiot.gsn.http.rest.LocalDeliveryWrapper; import org.openiot.gsn.http.rest.PushDelivery; import org.openiot.gsn.http.rest.RestDelivery; import org.openiot.gsn.metadata.LSM.LSMRepository; import org.openiot.gsn.storage.DBConnectionInfo; import org.openiot.gsn.storage.SQLValidator; import org.openiot.gsn.storage.StorageManager; import org.openiot.gsn.storage.StorageManagerFactory; import org.openiot.gsn.utils.PropertiesReader; import org.openiot.gsn.utils.ValidityTools; import org.openiot.gsn.vsensor.SQLValidatorIntegration; import org.openiot.gsn.wrappers.WrappersUtil; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; /** * Web Service URLs : Microsoft SensorMap: * http://localhost:22001/services/Service?wsdl GSN: * http://localhost:22001/services/GSNWebService?wsdl */ public final class Main { private static Main singleton; private static int gsnControllerPort; private Main() throws Exception { ValidityTools.checkAccessibilityOfFiles(DEFAULT_GSN_LOG4J_PROPERTIES, WrappersUtil.DEFAULT_WRAPPER_PROPERTIES_FILE, DEFAULT_GSN_CONF_FILE); ValidityTools .checkAccessibilityOfDirs(DEFAULT_VIRTUAL_SENSOR_DIRECTORY); PropertyConfigurator.configure(Main.DEFAULT_GSN_LOG4J_PROPERTIES); // initializeConfiguration(); try { controlSocket = new GSNController(null, gsnControllerPort); containerConfig = loadContainerConfiguration(); updateSplashIfNeeded(new String[] { "GSN is starting at port:" + containerConfig.getContainerPort(), "All GSN logs are available at: logs/gsn.log" }); System.out .println("Global Sensor Networks (GSN) is Starting on port " + containerConfig.getContainerPort() + "..."); System.out .println("The logs of GSN server are available in logs/gsn.log file."); System.out.println("To Stop GSN execute the gsn-stop script."); } catch (FileNotFoundException e) { logger.error(new StringBuilder() .append("The the configuration file : conf/gsn.xml") .append(" doesn't exist.").toString()); logger.error(e.getMessage()); logger.error("Check the path of the configuration file and try again."); if (logger.isDebugEnabled()) logger.debug(e.getMessage(), e); throw new Exception(e); } int maxDBConnections = System.getProperty("maxDBConnections") == null ? DEFAULT_MAX_DB_CONNECTIONS : Integer.parseInt(System.getProperty("maxDBConnections")); int maxSlidingDBConnections = System .getProperty("maxSlidingDBConnections") == null ? DEFAULT_MAX_DB_CONNECTIONS : Integer.parseInt(System .getProperty("maxSlidingDBConnections")); int maxServlets = System.getProperty("maxServlets") == null ? DEFAULT_JETTY_SERVLETS : Integer.parseInt(System.getProperty("maxServlets")); // Init the AC db connection. if (Main.getContainerConfig().isAcEnabled() == true) { ConnectToDB.init(containerConfig.getStorage().getJdbcDriver(), containerConfig.getStorage().getJdbcUsername(), containerConfig.getStorage().getJdbcPassword(), containerConfig.getStorage().getJdbcURL()); } mainStorage = StorageManagerFactory.getInstance(containerConfig .getStorage().getJdbcDriver(), containerConfig.getStorage() .getJdbcUsername(), containerConfig.getStorage() .getJdbcPassword(), containerConfig.getStorage().getJdbcURL(), maxDBConnections); // StorageConfig sc = containerConfig.getSliding() != null ? containerConfig .getSliding().getStorage() : containerConfig.getStorage(); windowStorage = StorageManagerFactory.getInstance(sc.getJdbcDriver(), sc.getJdbcUsername(), sc.getJdbcPassword(), sc.getJdbcURL(), maxSlidingDBConnections); // validationStorage = StorageManagerFactory.getInstance("org.h2.Driver", "sa", "", "jdbc:h2:mem:validator", Main.DEFAULT_MAX_DB_CONNECTIONS); if (logger.isInfoEnabled()) logger.info("The Container Configuration file loaded successfully."); try { logger.debug("Starting the http-server @ port: " + containerConfig.getContainerPort() + " (maxDBConnections: " + maxDBConnections + ", maxSlidingDBConnections: " + maxSlidingDBConnections + ", maxServlets:" + maxServlets + ")" + " ..."); Server jettyServer = getJettyServer(getContainerConfig() .getContainerPort(), getContainerConfig().getSSLPort(), maxServlets); jettyServer.start(); logger.debug("http-server running @ port: " + containerConfig.getContainerPort()); } catch (Exception e) { throw new Exception( "Start of the HTTP server failed. The HTTP protocol is used in most of the communications: " + e.getMessage(), e); } VSensorLoader vsloader = VSensorLoader .getInstance(DEFAULT_VIRTUAL_SENSOR_DIRECTORY); controlSocket.setLoader(vsloader); String msrIntegration = "gsn.msr.sensormap.SensorMapIntegration"; try { vsloader.addVSensorStateChangeListener((VSensorStateChangeListener) Class .forName(msrIntegration).newInstance()); } catch (Exception e) { logger.warn("MSR Sensor Map integration is disabled."); } vsloader.addVSensorStateChangeListener(new SQLValidatorIntegration( SQLValidator.getInstance())); vsloader.addVSensorStateChangeListener(DataDistributer .getInstance(LocalDeliveryWrapper.class)); vsloader.addVSensorStateChangeListener(DataDistributer .getInstance(PushDelivery.class)); vsloader.addVSensorStateChangeListener(DataDistributer .getInstance(RestDelivery.class)); ContainerImpl.getInstance().addVSensorDataListener( DataDistributer.getInstance(LocalDeliveryWrapper.class)); ContainerImpl.getInstance().addVSensorDataListener( DataDistributer.getInstance(PushDelivery.class)); ContainerImpl.getInstance().addVSensorDataListener( DataDistributer.getInstance(RestDelivery.class)); vsloader.startLoading(); } private static void closeSplashIfneeded() { if (isHeadless()) return; SplashScreen splash = SplashScreen.getSplashScreen(); // Check if we have specified any splash screen if (splash == null) { return; } if (splash.isVisible()) splash.close(); } private static void updateSplashIfNeeded(String message[]) { boolean headless_check = isHeadless(); for (int i = 0; i < message.length; i++) System.out.println(message[i]); if (!headless_check) { SplashScreen splash = SplashScreen.getSplashScreen(); if (splash == null) return; if (splash.isVisible()) { // Get a graphics overlay for the splash screen Graphics2D g = splash.createGraphics(); // Do some drawing on the graphics object // Now update to the splash screen g.setComposite(AlphaComposite.Clear); g.fillRect(0, 0, 400, 70); g.setPaintMode(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.BOLD, 11)); for (int i = 0; i < message.length; i++) g.drawString(message[i], 13, 16 * i + 10); splash.update(); } } } private static boolean isHeadless() { GraphicsEnvironment ge = GraphicsEnvironment .getLocalGraphicsEnvironment(); boolean headless_check = ge.isHeadless(); return headless_check; } public synchronized static Main getInstance() { if (singleton == null) try { singleton = new Main(); } catch (Exception e) { logger.error(e.getMessage(), e); throw new RuntimeException(e); } return singleton; } private static StorageManager mainStorage = null; private static StorageManager windowStorage = null; private static StorageManager validationStorage = null; private GSNController controlSocket; private static final int DEFAULT_JETTY_SERVLETS = 100; public static final int DEFAULT_MAX_DB_CONNECTIONS = 8; public static final String DEFAULT_GSN_LOG4J_PROPERTIES = "conf/log4j.properties"; public static transient Logger logger = Logger.getLogger(Main.class); public static final String DEFAULT_GSN_CONF_FILE = "conf/gsn.xml"; public static String DEFAULT_VIRTUAL_SENSOR_DIRECTORY = "virtual-sensors"; public static final String DEFAULT_WEB_APP_PATH = "webapp"; public static void main(String[] args) { Main.gsnControllerPort = Integer.parseInt(args[0]); updateSplashIfNeeded(new String[] { "GSN is trying to start.", "All GSN logs are available at: logs/gsn.log" }); try { Main.getInstance(); // checks to see if dynamic sensor control is active // and starts timer accordingly Config conf=ConfigFactory.load(); boolean isTrueDynamicControl = conf.getBoolean("dynamicControl"); if (isTrueDynamicControl) DynamicControlTaskTimer.getInstance().startTimer(); } catch (Exception e) { updateSplashIfNeeded(new String[] { "Starting GSN failed! Look at logs/gsn.log for more information." }); try { Thread.sleep(4000); } catch (InterruptedException e1) { e1.printStackTrace(); } } closeSplashIfneeded(); } /** * Mapping between the wrapper name (used in addressing of stream source) * into the class implementing DataSource. */ private static Properties wrappers; private ContainerConfig containerConfig; public static ContainerConfig loadContainerConfiguration() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, CertificateException, SecurityException, SignatureException, IOException { ValidityTools.checkAccessibilityOfFiles( Main.DEFAULT_GSN_LOG4J_PROPERTIES, WrappersUtil.DEFAULT_WRAPPER_PROPERTIES_FILE, Main.DEFAULT_GSN_CONF_FILE); ValidityTools .checkAccessibilityOfDirs(Main.DEFAULT_VIRTUAL_SENSOR_DIRECTORY); PropertyConfigurator.configure(Main.DEFAULT_GSN_LOG4J_PROPERTIES); ContainerConfig toReturn = null; try { toReturn = loadContainerConfig(DEFAULT_GSN_CONF_FILE); wrappers = WrappersUtil .loadWrappers(new HashMap<String, Class<?>>()); if (logger.isInfoEnabled()) logger.info(new StringBuilder() .append("Loading wrappers.properties at : ") .append(WrappersUtil.DEFAULT_WRAPPER_PROPERTIES_FILE) .toString()); if (logger.isInfoEnabled()) logger.info("Wrappers initialization ..."); } catch (JiBXException e) { logger.error(e.getMessage()); logger.error(new StringBuilder().append( "Can't parse the GSN configuration file : conf/gsn.xml") .toString()); logger.error("Please check the syntax of the file to be sure it is compatible with the requirements."); logger.error("You can find a sample configuration file from the GSN release."); if (logger.isDebugEnabled()) logger.debug(e.getMessage(), e); System.exit(1); } catch (FileNotFoundException e) { logger.error(new StringBuilder() .append("The the configuration file : conf/gsn.xml") .append(" doesn't exist.").toString()); logger.error(e.getMessage()); logger.error("Check the path of the configuration file and try again."); if (logger.isDebugEnabled()) logger.debug(e.getMessage(), e); System.exit(1); } catch (ClassNotFoundException e) { logger.error("The file wrapper.properties refers to one or more classes which don't exist in the classpath"); logger.error(e.getMessage(), e); System.exit(1); } finally { return toReturn; } } /** * This method is called by Rails's Application.rb file. */ public static ContainerConfig loadContainerConfig(String gsnXMLpath) throws JiBXException, FileNotFoundException, NoSuchAlgorithmException, NoSuchProviderException, IOException, KeyStoreException, CertificateException, SecurityException, SignatureException, InvalidKeyException, ClassNotFoundException { if (!new File(gsnXMLpath).isFile()) { logger.fatal("Couldn't find the gsn.xml file @: " + (new File(gsnXMLpath).getAbsolutePath())); System.exit(1); } IBindingFactory bfact = BindingDirectory .getFactory(ContainerConfig.class); IUnmarshallingContext uctx = bfact.createUnmarshallingContext(); ContainerConfig conf = (ContainerConfig) uctx.unmarshalDocument( new FileInputStream(new File(gsnXMLpath)), null); Class.forName(conf.getStorage().getJdbcDriver()); conf.setContainerConfigurationFileName(gsnXMLpath); return conf; } // FIXME: COPIED_FOR_SAFE_STOAGE public static Properties getWrappers() { if (singleton == null) return WrappersUtil.loadWrappers(new HashMap<String, Class<?>>()); return singleton.wrappers; } // FIXME: COPIED_FOR_SAFE_STOAGE public static Class<?> getWrapperClass(String id) { try { String className = getWrappers().getProperty(id); if (className == null) { logger.error("The requested wrapper: " + id + " doesn't exist in the wrappers.properties file."); return null; } return Class.forName(className); } catch (ClassNotFoundException e) { logger.error(e.getMessage(), e); } return null; } /** * Get's the GSN configuration without starting GSN. * * @return * @throws Exception */ public static ContainerConfig getContainerConfig() { if (singleton == null) try { return loadContainerConfig(DEFAULT_GSN_CONF_FILE); } catch (Exception e) { return null; } else return singleton.containerConfig; } public Server getJettyServer(int port, int sslPort, int maxThreads) throws IOException { Server server = new Server(); HandlerCollection handlers = new HandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection(); server.setThreadPool(new QueuedThreadPool(maxThreads)); SslSocketConnector sslSocketConnector = null; if (sslPort > 0) { System.out.println("SSL is Starting on port " + sslPort + "..."); sslSocketConnector = new SslSocketConnector(); sslSocketConnector.setPort(getContainerConfig().getSSLPort()); sslSocketConnector.setKeystore("conf/servertestkeystore"); sslSocketConnector.setPassword(getContainerConfig() .getSSLKeyPassword()); sslSocketConnector.setKeyPassword(getContainerConfig() .getSSLKeyStorePassword()); sslSocketConnector.setTruststore("conf/servertestkeystore"); sslSocketConnector.setTrustPassword(getContainerConfig() .getSSLKeyStorePassword()); } else if (getContainerConfig().isAcEnabled()) logger.error("SSL MUST be configured in the gsn.xml file when Access Control is enabled !"); // before was connector new SocketConnector(); // using basic connector for windows // bug; Fast option=>SelectChannelConnector AbstractConnector connector = new SelectChannelConnector(); connector.setPort(port); connector.setMaxIdleTime(30000); connector.setAcceptors(2); connector.setConfidentialPort(sslPort); if (sslSocketConnector == null) server.setConnectors(new Connector[] { connector }); else server.setConnectors(new Connector[] { connector, sslSocketConnector }); WebAppContext webAppContext = new WebAppContext(contexts, DEFAULT_WEB_APP_PATH, "/"); handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() }); server.setHandler(handlers); Properties usernames = new Properties(); usernames.load(new FileReader("conf/realm.properties")); if (!usernames.isEmpty()) { HashLoginService loginService = new HashLoginService(); loginService.setName("GSNRealm"); loginService.setConfig("conf/realm.properties"); loginService.setRefreshInterval(10000); // re-reads the file every // 10 seconds. Constraint constraint = new Constraint(); constraint.setName("GSN User"); constraint.setRoles(new String[] { "gsnuser" }); constraint.setAuthenticate(true); ConstraintMapping cm = new ConstraintMapping(); cm.setConstraint(constraint); cm.setPathSpec("/*"); cm.setMethod("GET"); ConstraintMapping cm2 = new ConstraintMapping(); cm2.setConstraint(constraint); cm2.setPathSpec("/*"); cm2.setMethod("POST"); ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler(); securityHandler.setLoginService(loginService); securityHandler.setConstraintMappings(new ConstraintMapping[] { cm, cm2 }); securityHandler.setAuthenticator(new BasicAuthenticator()); webAppContext.setSecurityHandler(securityHandler); } server.setSendServerVersion(true); server.setStopAtShutdown(true); server.setSendServerVersion(false); server.setSessionIdManager(new HashSessionIdManager(new Random())); return server; } public static StorageManager getValidationStorage() { return validationStorage; } private static HashMap<Integer, StorageManager> storages = new HashMap<Integer, StorageManager>(); private static HashMap<VSensorConfig, StorageManager> storagesConfigs = new HashMap<VSensorConfig, StorageManager>(); public static StorageManager getStorage(VSensorConfig config) { // StringBuilder sb = new // StringBuilder("get storage for: ").append(config == null ? null : // config.getName()).append(" -> use "); StorageManager sm = storagesConfigs.get(config == null ? null : config); if (sm != null) return sm; DBConnectionInfo dci = null; if (config == null || config.getStorage() == null || !config.getStorage().isDefined()) { // Use the default storage // sb.append("(default) config: ").append(config); sm = mainStorage; } else { // sb.append("(specific) "); // Use the virtual sensor specific storage if (config.getStorage().isIdentifierDefined()) { // TODO get the connection info with the identifier. throw new IllegalArgumentException( "Identifiers for storage is not supported yet."); } else { dci = new DBConnectionInfo(config.getStorage().getJdbcDriver(), config.getStorage().getJdbcURL(), config.getStorage() .getJdbcUsername(), config.getStorage() .getJdbcPassword()); } sm = storages.get(dci.hashCode()); if (sm == null) { sm = StorageManagerFactory.getInstance(config.getStorage() .getJdbcDriver(), config.getStorage().getJdbcUsername(), config .getStorage().getJdbcPassword(), config .getStorage().getJdbcURL(), DEFAULT_MAX_DB_CONNECTIONS); storages.put(dci.hashCode(), sm); storagesConfigs.put(config, sm); } } // sb.append("storage: ").append(sm.toString()).append(" dci: ").append(dci).append(" code: ").append(dci // == null ? null : dci.hashCode()); // if (logger.isDebugEnabled()) // logger.warn(sb.toString()); return sm; } public static StorageManager getStorage(String vsName) { return getStorage(Mappings.getVSensorConfig(vsName)); } public static StorageManager getDefaultStorage() { return getStorage((VSensorConfig) null); } public static StorageManager getWindowStorage() { return windowStorage; } }