// **********************************************************************
//
// <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.6 $
// $Date: 2004/10/14 18:06:00 $
// $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.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import com.bbn.openmap.layer.location.AbstractLocationHandler;
import com.bbn.openmap.layer.location.ByteRasterLocation;
import com.bbn.openmap.layer.location.Location;
import com.bbn.openmap.layer.location.LocationCBMenuItem;
import com.bbn.openmap.layer.location.LocationHandler;
import com.bbn.openmap.layer.location.LocationMenuItem;
import com.bbn.openmap.omGraphics.OMGraphic;
import com.bbn.openmap.omGraphics.OMGraphicList;
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 appropriate driver.
*/
protected String jdbcDriver = null;
/** Property to specify jdbc driver to loaded. */
public static final String jdbcDriverProperty = "jdbcDriver";
/** User name to be used for connecting 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 property 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 (logger.isLoggable(Level.FINE)) {
logger.fine("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) {
logger.warning("DBLocationHandler:SQL Exception: " + sqlE.getMessage());
sqlE.printStackTrace();
} catch (ClassNotFoundException cnfE) {
logger.warning("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 OMGraphicList get(double nwLat, double nwLon, double seLat, double seLon,
OMGraphicList graphicList) {
if (graphicList == null) {
graphicList = new OMGraphicList();
graphicList.setTraverseMode(OMGraphicList.FIRST_ADDED_ON_TOP);
}
// IF the quadtree has not been set up yet, do it!
if (quadtree == null) {
logger.fine("DBLocationHandler: Figuring out the locations and names! (This is a one-time operation!)");
quadtree = createData();
}
if (quadtree != null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("DBLocationHandler|DBLocationHandler.get() ul.lon = " + nwLon + " lr.lon = " + seLon + " delta = "
+ (seLon - nwLon));
}
Vector vec = new Vector<OMGraphic>();
quadtree.get(nwLat, nwLon, seLat, seLon, vec);
graphicList.addAll(vec);
}
return graphicList;
}
/*
* (non-Javadoc)
*
* @see
* com.bbn.openmap.layer.location.LocationHandler#getItemsForPopupMenu(com
* .bbn.openmap.layer.location.Location)
*/
public List<Component> getItemsForPopupMenu(Location loc) {
List<Component> menuItems = new ArrayList<Component>();
menuItems.add(new LocationCBMenuItem(LocationHandler.showname, loc));
menuItems.add(new LocationMenuItem(showdetails, loc));
return menuItems;
}
/** 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 (logger.isLoggable(Level.FINE)) {
logger.fine("DBLocationHandler::actionPerformed showLocations is " + isShowLocations());
}
getLayer().repaint();
} else if (cmd == showNamesCommand) {
JCheckBox namesCheck = (JCheckBox) e.getSource();
setShowNames(namesCheck.isSelected());
if (logger.isLoggable(Level.FINE)) {
logger.fine("DBLocationHandler::actionPerformed showNames is " + isShowNames());
}
getLayer().repaint();
} else if (cmd == readDataCommand) {
logger.fine("DBLocationHandler: Re-reading Locations file");
quadtree = null;
getLayer().doPrepare();
} else {
logger.warning("DBLocationHandler: Unknown action command \"" + cmd + "\" in actionPerformed().");
}
}
}