/*
* (C) Copyright IBM Corp. 2009
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb.apps.dashboard;
import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileOutputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
import javax.security.auth.login.LoginException;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JTabbedPane;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.ibm.gaiandb.Util;
import com.ibm.gaiandb.apps.DBConnector;
import com.ibm.gaiandb.apps.SecurityClientAgent;
/**
* <p>The GaianDB Dashboard is an application that lets the user monitor and query
* the GaianDB network using a connection to a specific node. It consists of a
* set of tabs that provide specific functions to the user.</p>
*
* <p><strong>Tabs:</strong></p>
* <ul>
* <li><em>Connection</em> - connects to and disconnects from the node
* specified</li>
* <li><em>Network Topology</em> - shows the network topology as a graph, with
* the ability to monitor one of several metrics across the network</li>
* <li><em>Node Statistics</em> - shows graphs of various metrics over time
* for a specific node</li>
* <li><em>SQL Queries</em> - provides an interface for the user to query the
* database using SQL</li>
* <li><em>Logival Tables and Data Sources</em> - show the federated view of logical tables and their data sources</li>
* </ul>
*
* @author Samir Talwar - stalwar@uk.ibm.com
*/
public class Dashboard extends JFrame {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2009";
private static final long serialVersionUID = 5198136049229737430L;
/**
* The application logger. Writes to <code>System.err</code> and
* {@link #LOG_FILE}.
*/
private static final Logger LOGGER = Logger.getLogger(Dashboard.class.getName());
/** The name of the dashboard log file. */
private static final String LOG_FILE = "dashboard.log";
/** The name of the prefuse log file. */
private static final String PREFUSE_LOG_FILE = "prefuse.log";
/** The starting size of the application window. */
static final Dimension DEFAULT_SIZE = new Dimension(900, 700); //700, 520); // 640, 480
/** The size of the gap between window components. */
static final int BORDER_SIZE = 10;
/** A border that can be used by components to provide default spacing. */
static final Border DEFAULT_BORDER = BorderFactory.createEmptyBorder(
BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE);
/** The time, in seconds, before a query times out. */
public static final int QUERY_TIMEOUT = 5;
/** Retrieves the ID of the local node, as defined by GaianDB. */
private static final String LOCAL_NODE_SQL =
"SELECT gdbx_to_node local_node FROM new com.ibm.db2j.GaianTable('gdb_ltnull', 'explain') T WHERE gdbx_depth = 0"; // from_node = '<SQL QUERY>'";
/** Connects to the database the user specifies. */
private DBConnector connector = new DBConnector();
/** The database connection. */
private Connection conn = null;
/** The set of window tabs. */
private Tab[] tabs;
private JTabbedPane tabbedPane = new JTabbedPane();
boolean isTopologyGraphAvailable = false;
/**
* A thread pool that handles talking to tabs, so we're not constantly
* spawning and dropping threads.
*/
private ExecutorService tabNotifier;
/** The name of the local node. */
private String localNodeID = null;
final SecurityClientAgent securityAgent = new SecurityClientAgent();
/**
* Sets up the various loggers - the application, prefuse and the
* DBConnector - and creates the window.
*
* @param args The application arguments. Unused.
*/
public static void main(String[] args) {
// Adds file output to the logger.
try {
Logger.getLogger(Dashboard.class.getPackage().getName()).addHandler(
new StreamHandler(new FileOutputStream(LOG_FILE), new SimpleFormatter()));
}
catch (Exception e) {
LOGGER.warning("Could not create the dashboard log file. Log messages will appear here only.");
}
// Stops prefuse from annoying the user and stores it all in a file instead.
Logger prefuseLogger = Logger.getLogger("prefuse");
prefuseLogger.setLevel(Level.WARNING);
for (Handler handler : prefuseLogger.getHandlers()) {
prefuseLogger.removeHandler(handler);
}
try {
prefuseLogger.addHandler(new StreamHandler(
new FileOutputStream(PREFUSE_LOG_FILE), new SimpleFormatter()));
}
catch (Exception e) {
LOGGER.warning("Could not create the prefuse log file. Prefuse errors will be ignored.");
}
// Stops the DBConnector from hassling us.
Logger.getLogger(DBConnector.class.getName()).setLevel(Level.OFF);
// Let's go.
Dashboard dashboard = new Dashboard();
dashboard.setVisible(true);
}
/**
* Constructs the dashboard window.
*/
public Dashboard() {
super("GaianDB Dashboard");
// Destroy the window when the Close button is hit.
// Any rampant threads will not be killed - they must end themselves
// or the application will not exit.
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
// Disconnects all tabs before closing. Any thread cleanup
// should be done in the tab's disconnect method.
disconnect();
tabNotifier.shutdown();
}
});
setPreferredSize(DEFAULT_SIZE);
setLocationByPlatform(true);
try {
// The JRE v5 doesn't support Windows' Aero Glass, but tries to
// anyway. This is bad, so we're trying to avoid it.
String javaVersionS = System.getProperty("java.version");
float javaVersion = Float.parseFloat(javaVersionS.substring(0, javaVersionS.indexOf('.', 3)));
if (Util.isWindowsOS) { //System.getProperty("os.name").startsWith("Windows")) {
float osVersion = Float.parseFloat(System.getProperty("os.version").split(" ")[0]);
if (javaVersion < 1.6 && osVersion >= 6) {
throw new Exception("Does not support Aero.");
}
}
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) {
// It doesn't matter if this doesn't work.
LOGGER.info("Cannot use the system's native look and feel.");
}
((JComponent)getContentPane()).setBorder(DEFAULT_BORDER);
// Set up the tabs.
// Each one has its own class for code management purposes.
try { getClass().getClassLoader().loadClass(TopologyGraph.class.getName()); isTopologyGraphAvailable = true;}
catch ( Throwable e ) { System.out.println("Could not load TopologyGraph (TopologyTab will not appear): " + e); }
tabs = isTopologyGraphAvailable ?
new Tab[] {
new ConnectionTab(this),
new TopologyTab(this),
// new LtAndDsTab(this),
new MonitorTab(this),
new StatsTab(this),
new QueryTab(this)
} :
new Tab[] {
new ConnectionTab(this),
// new TopologyTab(this),
// new LtAndDsTab(this),
new MonitorTab(this),
new StatsTab(this),
new QueryTab(this)
};
String[] tabNames = isTopologyGraphAvailable ?
new String[] {
"Connection",
"Network Topology",
// "Logical Tables",
"Current Metrics",
"Historical Metrics",
"SQL Queries"
} :
new String[] {
"Connection",
// "Network Topology",
// "Logical Tables",
"Current Metrics",
"Historical Metrics",
"SQL Queries"
};
for (int i = 0; i < tabs.length; i++)
tabbedPane.addTab(tabNames[i], tabs[i]);
add(tabbedPane);
// We need to notify the tabs when they're being activated/deactivated.
tabbedPane.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
final int selected = ((JTabbedPane)e.getSource()).getSelectedIndex();
if (selected >= 0 && selected < tabs.length) {
tabNotifier.execute(new Runnable() {
public void run() {
tabs[selected].activated();
}
});
}
for (final Tab tab : tabs) {
if (tab != tabs[selected]) {
tabNotifier.execute(new Runnable() {
public void run() {
tab.deactivated();
}
});
}
}
}
});
pack();
((ConnectionTab)tabs[0]).submit.requestFocusInWindow();
// Set up the tab notification thread pool.
tabNotifier = Executors.newFixedThreadPool(tabs.length);
}
private String lastURL = null;
private Properties lastInfo = null;
// This method must never be called before the first connect (the reconnect button in QueryTab is greyed out before then)
void reconnect() {
if ( null == lastURL || null == lastInfo ) return; // should never happen
((ConnectionTab) tabs[0]).connect( lastURL, lastInfo ); // Success with this will be confirmed in each tab as part of this method call
}
/**
* Connects the application to the database specified.
*
* @param url
* The database URL.
* @param user
* Your username.
* @param password
* Your password.
*
* @return True on success; false on failure.
*
* @throws ClassNotFoundException
* if the JDBC drivers cannot be found.
* @throws LoginException
*/
boolean connect(String url, Properties info) throws ClassNotFoundException, LoginException {
if (isConnected()) disconnect();
conn = connector.connect(url, info, false);
if ( false == isConnected() ) return false;
lastURL = url;
lastInfo = info;
try {
Statement statement = conn.createStatement();
statement.setQueryTimeout(Dashboard.QUERY_TIMEOUT);
ResultSet resultSet = statement.executeQuery(LOCAL_NODE_SQL);
if (resultSet.next())
localNodeID = resultSet.getString("LOCAL_NODE");
}
catch (SQLException e) {
LOGGER.warning(e.getMessage());
if ( false == checkConnection() ) return false;
}
// Create the metrics table by instantiating an unused metric monitor.
// MetricMonitor.getInstance(conn).stop();
for (final Tab tab : tabs) {
tabNotifier.execute(new Runnable() {
public void run() {
tab.connected(conn);
}
});
}
if ( isTopologyGraphAvailable && 0 == tabbedPane.getSelectedIndex() ) {
try { Thread.sleep(500); } catch (InterruptedException e) {}
tabbedPane.setSelectedIndex(1);
}
return true;
}
/**
* Disconnects from the current database.
*/
void disconnect() {
if (null != conn) {
try {
conn.close();
}
catch (SQLException e) {}
conn = null;
for (final Tab tab : tabs) {
tabNotifier.execute(new Runnable() {
public void run() {
tab.disconnected();
}
});
}
localNodeID = null;
}
}
/**
* Tests whether the database connection exists and is open.
*
* @return True if the connection is active; false otherwise.
*/
public boolean isConnected() {
try {
return conn != null && !conn.isClosed();
}
catch (SQLException e) {
conn = null;
return false;
}
}
/**
* <p>Checks the database connection. To be used when a database exception
* has been caught and the connection may not be alive.</p>
*
* <p>If the connection is no longer active, this will properly disconnect
* the application.</p>
*/
boolean checkConnection() {
if (null != conn) {
try {
if (conn.isClosed()) {
LOGGER.info("The connection has been terminated.");
disconnect();
((ConnectionTab)tabs[0]).showMessage("The connection has been terminated.");
return false;
}
}
catch (SQLException e) {
LOGGER.severe(e.getMessage());
disconnect();
((ConnectionTab)tabs[0]).showMessage("The connection has been terminated.");
return false;
}
}
return true;
}
String getLocalNodeID() {
return localNodeID;
}
// Used for path animation in topology tab.. not enabled yet..
public void updatePathVisualisation(int tickCount) {
((TopologyTab)tabs[1]).updatePathVisualisation(tickCount);
}
}