// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/location/db/DBLocationHandler.java,v $
// $RCSfile: DBLocationHandler.java,v $
// $Revision: 1.5.2.1 $
// $Date: 2004/10/14 18:27:10 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.location.db;
/* Java */
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.*;
import java.util.Properties;
import java.util.Vector;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
/* OpenMap */
import com.bbn.openmap.layer.location.*;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PropUtils;
import com.bbn.openmap.util.quadtree.QuadTree;
/**
* The DBLocationLayer is a LocationHandler designed to let you put
* data on the map based on information from a Database. The
* properties file lets you set defaults on whether to draw the
* locations and the names by default. For crowded layers, having all
* the names displayed might cause a cluttering problem. In gesture
* mode, OpenMap will display the name of each location as the mouse
* is passed over it. Pressing the left mouse button over a location
* brings up a popup menu that lets you show/hide the name label, and
* also to display attributes of the location in a Browser window that
* OpenMap launches.
*
* <P>
* If you want to extend the functionality of this LocationHandler,
* there are a couple of methods to focus your changes: The
* setProperties() method lets you add properties to set from the
* properties file. The createData() method, by default, is a one-time
* method that creates the graphic objects based on the data. By
* modifying these methods, and creating a different combination
* graphic other than the default LocationDataRecordSet, you can
* create different layer effects pretty easily.
*
* <P>
* In the openmap.properties file (for instance):
*
* <pre>
*
* # In the section for the LocationLayer:
* locationLayer.locationHandlers=dblocationhandler
*
* dblocationhandler.class=com.bbn.openmap.layer.location.db.DBLocationHandler
* dblocationhandler.locationColor=FF0000
* dblocationhandler.nameColor=008C54
* dblocationhandler.showNames=false
* dblocationhandler.showLocations=true
* dblocationhandler.jdbcDriver=oracle.jdbc.driver.OracleDriver
* dblocationhandler.jdbcString=jdbc login string
* dblocationhandler.userName=username
* dblocationhandler.userPassword=password
* dblocationhandler.locationQueryString=select statement the data
* object needs. See each Data object (like LocationData) to see what
* kind of select statement it needs.
*
* </pre>
*
* In addition, this particular location handler is using the
* LocationData object to handle the results from the location.
*/
public class DBLocationHandler extends AbstractLocationHandler implements
LocationHandler, ActionListener {
/** The storage mechanism for the locations. */
protected QuadTree quadtree = null;
//Database variables.
/*
* This String should be completely specified based on which
* Database is being used including username and password.
* Alternately, username and password can be specified by in
* properties file as jdbc.user=USERNAME jdbc.password=PASSWORD
*/
/** String that would be used for making a connection to Database */
protected String jdbcString = null;
/** Property that should be specified for setting jdbc String */
public static final String jdbcStringProperty = "jdbcString";
/**
* This String would be used to load the driver. If this string is
* null, jdbc Connection Manager would try to load the approriate
* driver.
*/
protected String jdbcDriver = null;
/** Property to specify jdbc driver to loaded. */
public static final String jdbcDriverProperty = "jdbcDriver";
/** User name to be used for connecing to DB. */
protected String userName = null;
/** Password to be used with for connecting to DB. */
protected String userPassword = null;
/** Property to specify userName for connecting to Database */
public static final String userNameProperty = "userName";
/** Property to specify password for connecting to Database */
public static final String userPasswordProperty = "userPassword";
/** Property to specify the query string passed to the database. */
public static final String locationQueryStringProperty = "locationQueryString";
/**
* The string used to query the database for the location
* information.
*/
protected String locationQueryString = null;
/** A copy of properties used to construct this Layer */
protected Properties props;
/**
* The default constructor for the Layer. All of the attributes
* are set to their default values.
*/
public DBLocationHandler() {}
/**
* The properties and prefix are managed and decoded here, for the
* standard uses of the DBLocationHandler.
*
* @param prefix string prefix used in the properties file for
* this layer.
* @param properties the properties set in the properties file.
*/
public void setProperties(String prefix, Properties properties) {
super.setProperties(prefix, properties);
prefix = getPropertyPrefix();
if (prefix != null) {
prefix = prefix + ".";
} else {
prefix = "";
}
props = properties; // Save it now. We would need it in future
jdbcString = properties.getProperty(prefix + jdbcStringProperty);
jdbcDriver = properties.getProperty(prefix + jdbcDriverProperty);
userName = properties.getProperty(prefix + userNameProperty);
userPassword = properties.getProperty(prefix + userPasswordProperty);
locationQueryString = properties.getProperty(prefix
+ locationQueryStringProperty);
}
/**
* PropertyConsumer method, to fill in a Properties object,
* reflecting the current values of the layer. If the layer has a
* propertyPrefix set, the property keys should have that prefix
* plus a separating '.' prepended to each propery key it uses for
* configuration.
*
* @param props a Properties object to load the PropertyConsumer
* properties into.
* @return Properties object containing PropertyConsumer property
* values. If getList was not null, this should equal
* getList. Otherwise, it should be the Properties object
* created by the PropertyConsumer.
*/
public Properties getProperties(Properties props) {
props = super.getProperties(props);
String prefix = getPropertyPrefix();
if (prefix != null) {
prefix = prefix + ".";
} else {
prefix = "";
}
props.put(prefix + "class", this.getClass().getName());
props.put(prefix + jdbcStringProperty, PropUtils.unnull(jdbcString));
props.put(prefix + jdbcDriverProperty, PropUtils.unnull(jdbcDriver));
props.put(prefix + userNameProperty, PropUtils.unnull(userName));
props.put(prefix + userPasswordProperty, PropUtils.unnull(userPassword));
props.put(prefix + locationQueryStringProperty,
PropUtils.unnull(locationQueryString));
// Put the properties in here for the RawDataRecordSet, which
// gets images that can be used for the locations.
props.put(prefix + RawDataRecordSet.tableNameProperty,
PropUtils.unnull(props.getProperty(prefix
+ RawDataRecordSet.tableNameProperty)));
props.put(prefix + RawDataRecordSet.rawDataColumnNameProperty,
PropUtils.unnull(props.getProperty(prefix
+ RawDataRecordSet.rawDataColumnNameProperty)));
props.put(prefix + RawDataRecordSet.rawDataKeyColumnNameProperty,
PropUtils.unnull(props.getProperty(prefix
+ RawDataRecordSet.rawDataKeyColumnNameProperty)));
return props;
}
/**
* Method to fill in a Properties object with values reflecting
* the properties able to be set on this PropertyConsumer. The key
* for each property should be the raw property name (without a
* prefix) with a value that is a String that describes what the
* property key represents, along with any other information about
* the property that would be helpful (range, default value,
* etc.). This method takes care of the basic LocationHandler
* parameters, so any LocationHandlers that extend the
* AbstractLocationHandler should call this method, too, before
* adding any specific properties.
*
* @param list a Properties object to load the PropertyConsumer
* properties into. If getList equals null, then a new
* Properties object should be created.
* @return Properties object containing PropertyConsumer property
* values. If getList was not null, this should equal
* getList. Otherwise, it should be the Properties object
* created by the PropertyConsumer.
*/
public Properties getPropertyInfo(Properties list) {
list = super.getPropertyInfo(list);
list.put("class" + ScopedEditorProperty,
"com.bbn.openmap.util.propertyEditor.NonEditablePropertyEditor");
list.put(jdbcStringProperty, "JDBC login string");
list.put(jdbcDriverProperty, "JDBC driver class name");
list.put(userNameProperty, "User name");
list.put(userPasswordProperty, "User password");
list.put(locationQueryStringProperty,
"Select statement that the data object needs.");
list.put(RawDataRecordSet.tableNameProperty,
"The name of the table in the database that holds the images.");
list.put(RawDataRecordSet.rawDataColumnNameProperty,
"The name of the column in the table in the database that holds the name (key) of the image.");
list.put(RawDataRecordSet.rawDataKeyColumnNameProperty,
"The name of the column in the table in the database that holds the raw image bytes.");
return list;
}
public void reloadData() {
quadtree = createData();
}
/**
* Look in the database and create the QuadTree holding all the
* Locations.
*/
protected QuadTree createData() {
QuadTree qt = new QuadTree(90.0f, -180.0f, -90.0f, 180.0f, 100, 50f);
ByteRasterLocation loc;
byte bytearr[];
if (locationQueryString == null) {
return qt;
}
// Code for reading from DB and pushing it into QuadTree.
try {
if (jdbcDriver != null) {
Class.forName(getJdbcDriver());
}
Connection connection = DriverManager.getConnection(getJdbcString(),
getUserName(),
getUserPassword());
RawDataRecordSet gifdataRS = new RawDataRecordSet(connection, getPropertyPrefix(), props);
RecordSet locationdataRS = new RecordSet(connection, locationQueryString);
while (locationdataRS.next()) {
LocationData ld = new LocationData(locationdataRS);
if (Debug.debugging("location")) {
Debug.output("DBLocationHandler: location information:\n"
+ ld);
}
bytearr = gifdataRS.getRawData(ld.getGraphicName());
float lat = ld.getLatitude();
float lon = ld.getLongitude();
loc = new ByteRasterLocation(lat, lon, ld.getCityName(), bytearr);
loc.setLocationHandler(this);
// let the layer handler default set these
// initially...
loc.setShowName(isShowNames());
loc.setShowLocation(isShowLocations());
loc.setLocationPaint(getLocationColor());
loc.getLabel().setLinePaint(getNameColor());
loc.setDetails(ld.getCityName() + " is at lat: " + lat
+ ", lon: " + lon);
qt.put(lat, lon, loc);
}
locationdataRS.close();
connection.close();
} catch (SQLException sqlE) {
Debug.error("DBLocationHandler:SQL Exception: " + sqlE.getMessage());
sqlE.printStackTrace();
} catch (ClassNotFoundException cnfE) {
Debug.error("DBLocationHandler: Class not found Exception: " + cnfE);
}
return qt;
}
public String getJdbcString() {
return jdbcString;
}
public String getJdbcDriver() {
return jdbcDriver;
}
public String getUserName() {
return userName;
}
public String getUserPassword() {
return userPassword;
}
public Properties getLocationProperties() {
return props;
}
/**
* Prepares the graphics for the layer. This is where the
* getRectangle() method call is made on the location.
* <p>
* Occasionally it is necessary to abort a prepare call. When this
* happens, the map will set the cancel bit in the LayerThread,
* (the thread that is running the prepare). If this Layer needs
* to do any cleanups during the abort, it should do so, but
* return out of the prepare asap.
*
*/
public Vector get(float nwLat, float nwLon, float seLat, float seLon,
Vector graphicList) {
// IF the quadtree has not been set up yet, do it!
if (quadtree == null) {
Debug.output("DBLocationHandler: Figuring out the locations and names! (This is a one-time operation!)");
quadtree = createData();
}
if (quadtree != null) {
if (Debug.debugging("location")) {
Debug.output("DBLocationHandler|DBLocationHandler.get() ul.lon = "
+ nwLon
+ " lr.lon = "
+ seLon
+ " delta = "
+ (seLon - nwLon));
}
quadtree.get(nwLat, nwLon, seLat, seLon, graphicList);
}
return graphicList;
}
public void fillLocationPopUpMenu(LocationPopupMenu locMenu) {
LocationCBMenuItem lcbi = new LocationCBMenuItem(LocationHandler.showname, locMenu, getLayer());
lcbi.setState(locMenu.getLoc().isShowName());
locMenu.add(lcbi);
locMenu.add(new LocationMenuItem(showdetails, locMenu, getLayer()));
}
/** Box used for constructing the palette widgets */
protected Box box = null;
/**
* Provides the palette widgets to control the options of showing
* maps, or attribute text.
*
* @return Component object representing the palette widgets.
*/
public Component getGUI() {
if (box == null) {
JCheckBox showDBLocationCheck, showNameCheck;
JButton rereadFilesButton;
showDBLocationCheck = new JCheckBox("Show Locations", isShowLocations());
showDBLocationCheck.setActionCommand(showLocationsCommand);
showDBLocationCheck.addActionListener(this);
showNameCheck = new JCheckBox("Show Location Names", isShowNames());
showNameCheck.setActionCommand(showNamesCommand);
showNameCheck.addActionListener(this);
rereadFilesButton = new JButton("Reload Data From Source");
rereadFilesButton.setActionCommand(readDataCommand);
rereadFilesButton.addActionListener(this);
box = Box.createVerticalBox();
box.add(showDBLocationCheck);
box.add(showNameCheck);
box.add(rereadFilesButton);
}
return box;
}
//----------------------------------------------------------------------
// ActionListener interface implementation
//----------------------------------------------------------------------
/**
* The Action Listener method, that reacts to the palette widgets
* actions.
*/
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd == showLocationsCommand) {
JCheckBox locationCheck = (JCheckBox) e.getSource();
setShowLocations(locationCheck.isSelected());
if (Debug.debugging("location")) {
Debug.output("DBLocationHandler::actionPerformed showLocations is "
+ isShowLocations());
}
getLayer().repaint();
} else if (cmd == showNamesCommand) {
JCheckBox namesCheck = (JCheckBox) e.getSource();
setShowNames(namesCheck.isSelected());
if (Debug.debugging("location")) {
Debug.output("DBLocationHandler::actionPerformed showNames is "
+ isShowNames());
}
getLayer().repaint();
} else if (cmd == readDataCommand) {
Debug.output("DBLocationHandler: Re-reading Locations file");
quadtree = null;
getLayer().doPrepare();
} else {
Debug.error("DBLocationHandler: Unknown action command \"" + cmd
+ "\" in actionPerformed().");
}
}
}