/* * Copyright 2011 Ronald Kurniawan. * * This file is part of CodeTraq. * * CodeTraq is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CodeTraq 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CodeTraq. If not, see <http://www.gnu.org/licenses/>. */ package net.mobid.codetraq; import java.io.IOException; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import net.mobid.codetraq.persistence.ServerDTO; import net.mobid.codetraq.persistence.UserDTO; import net.mobid.codetraq.runnables.GitChecker; import net.mobid.codetraq.runnables.MessageTracker; import net.mobid.codetraq.runnables.ServerTracker; import net.mobid.codetraq.runnables.SvnChecker; import net.mobid.codetraq.talkers.EmailTalker; import net.mobid.codetraq.utils.DbUtility; import net.mobid.codetraq.utils.LogService; import net.mobid.codetraq.utils.PasswordProcessor; import net.mobid.codetraq.utils.Utilities; import net.mobid.codetraq.talkers.MSNTalker; import net.mobid.codetraq.talkers.XMPPTalker; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * This is the entry point for the CodeTraq daemon program. Setup all necessary * component, then run all the runnables. * * @author Ronald Kurniawan * @version 0.1 */ public class Main { private static boolean _isRunning = true; private static ITalker _xmppTalker = null; private static ITalker _msnTalker = null; private static ITalker _mailTalker = null; private static Document _configuration = null; private static List<UserDTO> _users = null; private static List<ServerDTO> _servers = null; private volatile Thread _messageChecker = null; private volatile Thread _serverChecker = null; private static DbUtility _traqdb = null; private final int USER_UPDATE_IN_MINUTES = 8; /** * Read and parse CodeTraq configuration file (ctraq.xml) in current directory. * @return <code>Document</code> object containing the parsed configuration */ public Document getConfiguration() { if (_configuration == null) { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); _configuration = builder.parse(new File("ctraq.xml")); _configuration.getDocumentElement().normalize(); } catch (SAXException ex) { LogService.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); LogService.writeLog(Level.SEVERE, ex); } catch (IOException ex) { LogService.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); LogService.writeLog(Level.SEVERE, ex); } catch (ParserConfigurationException pae) { LogService.getLogger(Main.class.getName()).log(Level.SEVERE, null, pae); LogService.writeLog(Level.SEVERE, pae); } } return _configuration; } /** * Returns all the users in the configuration file. * @return a <code>List</code> containing all the users that will receive notifications. */ public List<UserDTO> getUsers() { if (_users == null) { _users = new ArrayList<UserDTO>(); } return _users; } /** * Returns all the servers in the configuration file. * @return a <code>List</code> containing all the servers that should be monitored. */ public List<ServerDTO> getServers() { if (_servers == null) { _servers = new ArrayList<ServerDTO>(); } return _servers; } public static void main(String[] args) { Main m = new Main(); } /** * Constructor for Main class. * <p>Initialises the application, reads the configuration file and call run().</p> */ public Main() { System.out.printf("CodeTraq daemon v0.1%n"); // we need to have a config file. Check for existing one or create a new one... // and also, we need to read the configuration file for any errors.... boolean setupStatus = true; if (!isConfigFileExists()) { setupStatus = false; createConfigFile(); } if (!readConfigFile() && setupStatus) { setupStatus = false; } if (!setupStatus) { System.out.printf("Your daemon has not been configured properly. Please populate " + "the configuration file (ctraq.xml) with correct values and restart the daemon.%n"); return; } _traqdb = new DbUtility(); ShutdownHook hook = new ShutdownHook(); Runtime.getRuntime().addShutdownHook(hook); System.out.printf("CodeTraq started on %s%n", Utilities.getFormattedTime()); LogService.writeMessage("CodeTraq started"); run(); } /* * Creates an empty configuration file. */ private void createConfigFile() { Utilities.createNewConfigFile("ctraq.xml"); } /* * Checks whether configuration file exists. * @return <code>true</code> if configuration file exists, <code>false</code> otherwise. */ private boolean isConfigFileExists() { File cfg = new File("ctraq.xml"); if (cfg.exists()) { return true; } return false; } /* * Reads the configuration file and sets up internal variables. * * @return <code>true</code> if configuration file has the right syntax, * <code>false</code> if reader encounters any errors. */ private boolean readConfigFile() { try { // get codetraq notification id NodeList traqs = getConfiguration().getElementsByTagName("traq"); if (traqs.getLength() > 0) { for (int i = 0; i < traqs.getLength(); i++) { Node traq = traqs.item(i); if (!Utilities.checkNode("traq type", traq.getAttributes().getNamedItem("type")) || !Utilities.checkNode("traq notification id", traq.getAttributes().getNamedItem("notificationid")) || !Utilities.checkNode("traq notification password", traq.getAttributes().getNamedItem("password"))) { System.out.printf("Please review your configuration file%n"); return false; } String type = traq.getAttributes().getNamedItem("type").getTextContent(); String notificationId = traq.getAttributes().getNamedItem("notificationid").getTextContent(); String password = PasswordProcessor.decryptString(traq.getAttributes(). getNamedItem("password").getTextContent()); if (!Utilities.checkValue("daemon notification type", type) || !Utilities.checkValue("daemon notification password", password) || !Utilities.checkValue("daemon notification id", notificationId)) { return false; } String smtpHost = ""; int smtpPort = 0; boolean smtpSsl = false; boolean smtpTls = false; if (type.equalsIgnoreCase("email")) { if (!Utilities.checkNode("traq smtp host", traq.getAttributes().getNamedItem("host")) || !Utilities.checkNode("traq smtp port", traq.getAttributes().getNamedItem("port")) || !Utilities.checkNode("traq using ssl", traq.getAttributes().getNamedItem("ssl")) || !Utilities.checkNode("traq using tls", traq.getAttributes().getNamedItem("tls"))) { System.out.printf("Please review your configuration file%n"); return false; } smtpHost = traq.getAttributes().getNamedItem("host").getTextContent(); if (!Utilities.checkValue("daemon smtp host", smtpHost)) { return false; } String tPort = traq.getAttributes().getNamedItem("port").getTextContent(); try { smtpPort = Integer.parseInt(tPort); } catch (NumberFormatException nfe) { LogService.getLogger(Main.class.getName()).log(Level.SEVERE, null, nfe); LogService.writeLog(Level.SEVERE, nfe); System.out.printf("traq smtp port should be a number between 1 - 65535. Please review your configuration file.%n"); return false; } String tSsl = traq.getAttributes().getNamedItem("ssl").getTextContent(); if (tSsl.equalsIgnoreCase("true") || tSsl.equalsIgnoreCase("yes")) { smtpSsl = true; } String tTls = traq.getAttributes().getNamedItem("tls").getTextContent(); if (tTls.equalsIgnoreCase("true") || tTls.equalsIgnoreCase("yes")) { smtpTls = true; } } // it is possible that a second Talker implementing another notification type // (e.g. "msn") might be implemented in the future. In that case, add another // "else if" block below... if (type.equalsIgnoreCase("gtalk") || type.equalsIgnoreCase("jabber")) { if (_xmppTalker == null) { ConnectionType cType = ConnectionType.JABBER; if (type.equalsIgnoreCase("gtalk")) { cType = ConnectionType.GOOGLE_TALK; } _xmppTalker = new XMPPTalker(notificationId, password, cType); } } else if (type.equalsIgnoreCase("msn")) { if (_msnTalker == null) { ConnectionType cType = ConnectionType.MSN; _msnTalker = new MSNTalker(notificationId, password, cType); } } else if (type.equalsIgnoreCase("email")) { if (_mailTalker == null) { _mailTalker = new EmailTalker(notificationId, password, smtpHost, smtpPort, smtpSsl, smtpTls); } } else { return false; } } } // get list of users and servers NodeList users = getConfiguration().getElementsByTagName("user"); for (int i = 0; i < users.getLength(); i++) { Node user = users.item(i); if (!Utilities.checkNode("user name", user.getAttributes().getNamedItem("name")) || !Utilities.checkNode("user id", user.getAttributes().getNamedItem("id")) || !Utilities.checkNode("user notification type", user.getAttributes().getNamedItem("type")) || !Utilities.checkNode("user notification id", user.getAttributes().getNamedItem("notificationid"))) { System.out.printf("Please review your configuration file.%n"); return false; } String name = user.getAttributes().getNamedItem("name").getTextContent(); String id = user.getAttributes().getNamedItem("id").getTextContent(); String type = user.getAttributes().getNamedItem("type").getTextContent(); String notificationId = user.getAttributes().getNamedItem("notificationid").getTextContent(); if (!Utilities.checkValue("user nickname", name) || !Utilities.checkValue("user id", id) || !Utilities.checkValue("user notification type", type) || !Utilities.checkValue("user notification id", notificationId)) { return false; } UserDTO u = new UserDTO(); u.setId(id); u.setNickname(name); u.setNotificationId(notificationId); if (type.equalsIgnoreCase("gtalk") || type.equalsIgnoreCase("jabber")) { u.setNotificationType(type.equalsIgnoreCase("gtalk") ? ConnectionType.GOOGLE_TALK : ConnectionType.JABBER); getUsers().add(u); // check whether this contact is already in our contact list if (!_xmppTalker.isInContactList(notificationId)) { _xmppTalker.addToContactList(notificationId); } } else if (type.equalsIgnoreCase("msn")) { u.setNotificationType(ConnectionType.MSN); getUsers().add(u); // check if contact is in our contact list // because we need to wait for the messenger to be available, // we pack this into a pending list ((MSNTalker) _msnTalker).getPendingCheckUser().add(notificationId); } else if (type.equalsIgnoreCase("email")) { u.setNotificationType(ConnectionType.EMAIL); getUsers().add(u); } } NodeList servers = getConfiguration().getElementsByTagName("server"); for (int i = 0; i < servers.getLength(); i++) { Node server = servers.item(i); if (!Utilities.checkNode("server owner", server.getAttributes().getNamedItem("owner")) || !Utilities.checkNode("server nickname", server.getAttributes().getNamedItem("sname")) || !Utilities.checkNode("server address", server.getAttributes().getNamedItem("address")) || !Utilities.checkNode("server type", server.getAttributes().getNamedItem("type")) || !Utilities.checkNode("server username", server.getAttributes().getNamedItem("username")) || !Utilities.checkNode("server password", server.getAttributes().getNamedItem("password"))) { System.out.printf("Please review your configuration file.%n"); return false; } String owner = server.getAttributes().getNamedItem("owner").getTextContent(); String shortName = server.getAttributes().getNamedItem("sname").getTextContent(); String sAddress = server.getAttributes().getNamedItem("address").getTextContent(); String sType = server.getAttributes().getNamedItem("type").getTextContent(); String sUsername = server.getAttributes().getNamedItem("username").getTextContent(); String sPassword = PasswordProcessor.decryptString(server.getAttributes(). getNamedItem("password").getTextContent()); if (!Utilities.checkValue("owner id", owner) || !Utilities.checkValue("server name", shortName) || !Utilities.checkValue("server address", sAddress) || !Utilities.checkValue("server type", sType) || !Utilities.checkValue("server username", sUsername) || !Utilities.checkValue("server password", sPassword)) { return false; } // need to check that server's short name is unique, as we need it to // create git local repo (if type is 'git') if (!Utilities.checkServerShortName(getServers(), shortName)) { System.out.printf("Server short name must be unique. '%s' is already used for another server.%n", shortName); return false; } // if this is a GIT repo, we need a branch name String sBranch = null; if (sType.equalsIgnoreCase("git")) { if (!Utilities.checkNode("git branch", server.getAttributes().getNamedItem("branch"))) { System.out.printf("GIT server %s does not have a specified branch. Please review your configuration file.%n", sAddress); return false; } sBranch = server.getAttributes().getNamedItem("branch").getTextContent(); if (!Utilities.checkValue("git branch", sBranch)) { return false; } } ServerDTO s = new ServerDTO(); s.setOwnerId(owner); s.setShortName(shortName); if (sType.equalsIgnoreCase("svn")) { s.setServerType(VersionControlType.SVN); } else if (sType.equalsIgnoreCase("git")) { s.setServerType(VersionControlType.GIT); s.setServerBranch(sBranch); } s.setServerAddress(sAddress); s.setServerUsername(sUsername); s.setServerPassword(sPassword); getServers().add(s); } } catch (Exception ex) { LogService.writeLog(Level.SEVERE, ex); } // if encountering any error return false return true; } /** * Starts all the runnables that monitor the servers and notify the users. */ public final void run() { MessageTracker tracker = new MessageTracker(_traqdb); tracker.setXMPPTalker(_xmppTalker); tracker.setMSNTalker(_msnTalker); tracker.setEmailTalker(_mailTalker); _messageChecker = new Thread(tracker); _messageChecker.start(); ServerTracker st = new ServerTracker(_traqdb, _servers); _serverChecker = new Thread(st); _serverChecker.start(); while (_isRunning) { for (int i = 0; i < _servers.size(); i++) { ServerDTO s = _servers.get(i); UserDTO user = getUserById(s.getOwnerId()); if (user == null) { System.out.printf("Cannot find user %s%n.", s.getOwnerId()); LogService.writeMessage("Cannot find user " + s.getOwnerId()); continue; } if (s.getServerType() == VersionControlType.SVN) { SvnChecker svnChecker = new SvnChecker(s, user, _traqdb); Thread t = new Thread(svnChecker); t.start(); } else if (s.getServerType() == VersionControlType.GIT) { GitChecker gitChecker = new GitChecker(s, user, _traqdb); Thread t = new Thread(gitChecker); t.start(); } } try { Thread.sleep(USER_UPDATE_IN_MINUTES * 60 * 1000); } catch (InterruptedException ex) { LogService.writeLog(Level.SEVERE, ex); } } } /* * Search the internal list _users for a User with specified ID. * @param id - user specified ID * @return a <code>UserDTO</code> object. */ private UserDTO getUserById(String id) { UserDTO key = new UserDTO(); key.setId(id); Collections.sort(_users); int x = Collections.binarySearch(_users, key); if (x < 0) { return null; } return _users.get(x); } /* * Stops the message checker thread. Should only be called during shutdown. */ private synchronized void messageTrackerStop() { Thread tmpChecker = _messageChecker; _messageChecker = null; if (tmpChecker != null) { tmpChecker.interrupt(); } } /* * Stops the server tracker thread. Should only be called during shutdown. */ private synchronized void serverTrackerStop() { Thread tmpChecker = _serverChecker; _serverChecker = null; if (tmpChecker != null) { tmpChecker.interrupt(); } } /* * ShutdownHook class is a class that is responsible for "cleaning up" during * shutdown process. It stops every running thread and cleans up internal variables. * */ class ShutdownHook extends Thread { // we run "cleaning process" here @Override public void run() { System.out.printf("Shutting down CodeTraq...%n"); if (_xmppTalker != null) { _xmppTalker.disconnect(); _xmppTalker = null; } if (_msnTalker != null) { _msnTalker.disconnect(); _msnTalker = null; } if (_mailTalker != null) { _mailTalker = null; } _isRunning = false; _traqdb.closeDbs(); messageTrackerStop(); serverTrackerStop(); _users.clear(); _users = null; _servers.clear(); _servers = null; _configuration = null; LogService.writeMessage("CodeTraq shut down"); } } }