Issue #1409 Initial implementation of hydro data access

Change-Id: I309694f9477384446658520794e19ca9493473fb

Former-commit-id: c236b4e1ad [formerly 817717fe0a] [formerly c236b4e1ad [formerly 817717fe0a] [formerly 85658e7e1d [formerly ae7ac8a8bfc51d1c7dc3493c8fc036a87772f323]]]
Former-commit-id: 85658e7e1d
Former-commit-id: 94b9d10c81 [formerly 3c2865bbd7]
Former-commit-id: 13dca3b784
This commit is contained in:
Nate Jensen 2012-12-11 09:29:31 -06:00
parent 4a539b10ce
commit 1aa8d304df
10 changed files with 697 additions and 26 deletions

View file

@ -312,4 +312,11 @@
version="0.0.0"
unpack="false"/>
<plugin
id="com.raytheon.uf.common.dataaccess"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>

View file

@ -85,6 +85,7 @@ public class DataFactoryRegistry {
*/
public IDataFactory<?, ?> register(String datatype,
Class<IDataRequest<?>> requestType, IDataFactory<?, ?> factory) {
datatype = datatype.toLowerCase();
Map<Class<IDataRequest<?>>, IDataFactory<?, ?>> requestTypeMap = datatypeMap
.get(datatype);
if (requestTypeMap == null) {
@ -109,7 +110,7 @@ public class DataFactoryRegistry {
@SuppressWarnings("unchecked")
public <R extends IDataRequest<D>, D extends IData> IDataFactory<R, D> getFactory(
R request) {
String datatype = request.getDatatype();
String datatype = request.getDatatype().toLowerCase();
if (datatype != null) {
Map<Class<IDataRequest<?>>, IDataFactory<?, ?>> requestTypeMap = datatypeMap
.get(datatype);

View file

@ -24,7 +24,6 @@ import javax.measure.unit.Unit;
import org.geotools.coverage.grid.GridGeometry2D;
import com.raytheon.uf.common.dataaccess.IData;
import com.raytheon.uf.common.dataplugin.level.Level;
import com.raytheon.uf.common.geospatial.interpolation.data.DataDestination;
import com.raytheon.uf.common.geospatial.interpolation.data.UnitConvertingDataDestination;
@ -55,13 +54,6 @@ public interface IGridData extends IData {
*/
public String getParameter();
/**
* Gets the level of the data
*
* @return the level of the data
*/
public Level getLevel();
/**
* Gets the GridGeometry of the data
*

View file

@ -4,11 +4,10 @@ Bundle-Name: Hydrocommon Plug-in
Bundle-SymbolicName: com.raytheon.uf.common.hydro
Bundle-Version: 1.12.1174.qualifier
Bundle-Vendor: RAYTHEON
Require-Bundle: org.eclipse.ui,
org.eclipse.core.runtime,
org.geotools;bundle-version="2.5.2",
Require-Bundle: org.geotools;bundle-version="2.5.2",
com.raytheon.uf.common.geospatial;bundle-version="1.11.9",
com.raytheon.edex.common;bundle-version="1.11.9"
com.raytheon.edex.common;bundle-version="1.11.9",
com.raytheon.uf.common.dataaccess
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Bundle-ActivationPolicy: lazy
Export-Package: com.raytheon.uf.common.hydro.service,
@ -16,6 +15,7 @@ Export-Package: com.raytheon.uf.common.hydro.service,
Import-Package: com.raytheon.uf.common.localization,
com.raytheon.uf.common.menus,
com.raytheon.uf.common.menus.xml,
com.raytheon.uf.common.message.response,
com.raytheon.uf.common.ohd,
com.raytheon.uf.common.serialization.comm,
org.apache.commons.logging

View file

@ -1,4 +1,5 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
.,\
res/

View file

@ -1,8 +0,0 @@
<project basedir="." default="deploy" name="com.raytheon.uf.common.hydro">
<available file="../build.edex" property="build.dir.location" value="../build.edex"/>
<available file="../../../../../build.edex" property="build.dir.location" value="../../../../../build.edex"/>
<import file="${build.dir.location}/basebuilds/component_deploy_base.xml" />
</project>

View file

@ -0,0 +1,13 @@
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="hydroDataFactory" class="com.raytheon.uf.common.hydro.dataaccess.HydroGeometryFactory" />
<bean factory-bean="dataAccessRegistry" factory-method="register">
<constructor-arg value="ihfsData"/>
<constructor-arg value="com.raytheon.uf.common.dataaccess.geom.IGeometryRequest"/>
<constructor-arg ref="hydroDataFactory"/>
</bean>
</beans>

View file

@ -0,0 +1,251 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.hydro.dataaccess;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.raytheon.uf.common.dataaccess.exception.DataRetrievalException;
import com.raytheon.uf.common.dataaccess.exception.TimeAgnosticDataException;
import com.raytheon.uf.common.dataaccess.geom.IGeometryData;
import com.raytheon.uf.common.dataaccess.geom.IGeometryDataFactory;
import com.raytheon.uf.common.dataaccess.geom.IGeometryRequest;
import com.raytheon.uf.common.dataaccess.impl.AbstractDataFactory;
import com.raytheon.uf.common.dataaccess.impl.DefaultGeometryData;
import com.raytheon.uf.common.dataaccess.impl.FactoryUtil;
import com.raytheon.uf.common.dataquery.db.QueryResult;
import com.raytheon.uf.common.dataquery.db.QueryResultRow;
import com.raytheon.uf.common.dataquery.requests.QlServerRequest;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint;
import com.raytheon.uf.common.message.response.AbstractResponseMessage;
import com.raytheon.uf.common.message.response.ResponseMessageError;
import com.raytheon.uf.common.message.response.ResponseMessageGeneric;
import com.raytheon.uf.common.serialization.comm.RequestRouter;
import com.raytheon.uf.common.time.BinOffset;
import com.raytheon.uf.common.time.DataTime;
import com.raytheon.uf.common.time.TimeRange;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
/**
* A data factory for getting data from the IHFS database. Requires that a
* request have a table identifier that corresponds to the table it should
* retrieve data from. Only works against tables that follow the SHEF PEDTSEP
* pattern.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Nov 13, 2012 njensen Initial creation
*
* </pre>
*
* @author njensen
* @version 1.0
*/
public class HydroGeometryFactory extends AbstractDataFactory implements
IGeometryDataFactory {
// TODO always require at least one PE
// TODO possibly take care of it for them and add value
// TODO potentially limit big requests so that if too big of a result set,
// throw error to
// let the user know they need to reduce amount of data they request
// test out on live data how slow it gets to determine max number
// TODO add support for envelopes bounding the request
private static final String[] REQUIRED = { HydroQueryAssembler.TABLE };
private GeometryFactory gisFactory = new GeometryFactory();
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.dataaccess.IDataFactory#getAvailableTimes(com.
* raytheon.uf.common.dataaccess.IDataRequest)
*/
@Override
public DataTime[] getAvailableTimes(IGeometryRequest request)
throws TimeAgnosticDataException {
validateRequest(request);
String query = HydroQueryAssembler.assembleGetTimes(request);
List<Object[]> result = sendServerRequest(query);
DataTime[] dts = new DataTime[result.size()];
for (int i = 0; i < dts.length; i++) {
dts[i] = new DataTime((Timestamp) result.get(i)[0]);
}
return dts;
}
@Override
public DataTime[] getAvailableTimes(IGeometryRequest request,
BinOffset binOffset) throws TimeAgnosticDataException {
return FactoryUtil.getAvailableTimes(this, request, binOffset);
}
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.dataaccess.IDataFactory#getData(com.raytheon.uf
* .common.dataaccess.IDataRequest, com.raytheon.uf.common.time.DataTime[])
*/
@Override
public IGeometryData[] getData(IGeometryRequest request, DataTime... times) {
validateRequest(request);
String query = HydroQueryAssembler.assembleGetData(request, times);
List<Object[]> result = sendServerRequest(query);
return makeGeometries(result, request.getParameters(),
request.getIdentifiers());
}
/*
* (non-Javadoc)
*
* @see
* com.raytheon.uf.common.dataaccess.IDataFactory#getData(com.raytheon.uf
* .common.dataaccess.IDataRequest, com.raytheon.uf.common.time.TimeRange)
*/
@Override
public IGeometryData[] getData(IGeometryRequest request, TimeRange timeRange) {
validateRequest(request);
String query = HydroQueryAssembler.assembleGetData(request, timeRange);
List<Object[]> result = sendServerRequest(query);
return makeGeometries(result, request.getParameters(),
request.getIdentifiers());
}
/*
* (non-Javadoc)
*
* @see com.raytheon.uf.common.dataaccess.geom.IGeometryDataFactory#
* getAvailableLocationNames
* (com.raytheon.uf.common.dataaccess.geom.IGeometryRequest)
*/
@Override
public String[] getAvailableLocationNames(IGeometryRequest request) {
String query = "select lid from location;";
List<Object[]> results = sendServerRequest(query);
int size = results.size();
String[] locations = new String[size];
for (int i = 0; i < size; i++) {
locations[i] = (String) results.get(i)[0];
}
return locations;
}
@Override
public String[] getRequiredIdentifiers() {
return REQUIRED;
}
/**
* Sends the query to the server and returns the results
*
* @param query
* the query to run
* @return the results of the query
*/
private List<Object[]> sendServerRequest(String query) {
Map<String, RequestConstraint> rcMap = new HashMap<String, RequestConstraint>();
rcMap.put("query", new RequestConstraint(query));
rcMap.put("database", new RequestConstraint("ihfs"));
rcMap.put("mode", new RequestConstraint("sqlquery"));
QlServerRequest qsr = new QlServerRequest(rcMap);
AbstractResponseMessage response = null;
try {
response = (AbstractResponseMessage) RequestRouter.route(qsr);
} catch (Exception e) {
throw new DataRetrievalException("Error retrieving IHFS data", e);
}
QueryResult result = null;
if (response instanceof ResponseMessageError) {
throw new DataRetrievalException("Error retrieving IHFS data: "
+ response.toString());
} else if (response instanceof ResponseMessageGeneric) {
result = (QueryResult) ((ResponseMessageGeneric) response)
.getContents();
} else {
throw new DataRetrievalException(
"Unable to process response of type" + response.getClass());
}
List<Object[]> unmappedResults = new ArrayList<Object[]>();
for (QueryResultRow row : result.getRows()) {
unmappedResults.add(row.getColumnValues());
}
return unmappedResults;
}
/**
* Builds the data objects that will be returned by calls to getData() on
* the factory
*
* @param serverResult
* the results from the query run on the server
* @param paramNames
* the names of the parameters that were requested
* @param identifiers
* the identifiers from the data request
* @return the IGeometryData based on the results of the query
*/
private IGeometryData[] makeGeometries(List<Object[]> serverResult,
String[] paramNames, Map<String, Object> identifiers) {
List<IGeometryData> resultList = new ArrayList<IGeometryData>();
Map<String, Object> attrs = Collections.unmodifiableMap(identifiers);
// loop over each db row
for (Object[] row : serverResult) {
DefaultGeometryData geom = new DefaultGeometryData();
// order is lid, producttime, lat, lon, other params
String lid = (String) row[0];
Timestamp date = (Timestamp) row[1];
double lat = (Double) row[2];
double lon = (Double) row[3];
if (row.length > 4) {
for (int i = 4; i < row.length; i++) {
String name = paramNames[i - 4];
geom.addData(name, row[i]);
}
}
geom.setLocationName(lid);
geom.setDataTime(new DataTime(date));
// intentionally setting level as null until hydrologists determine
// something better
geom.setLevel(null);
geom.setGeometry(gisFactory.createPoint(new Coordinate(lon, lat)));
geom.setAttributes(attrs);
resultList.add(geom);
}
return resultList.toArray(new DefaultGeometryData[0]);
}
}

View file

@ -0,0 +1,407 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.hydro.dataaccess;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.raytheon.uf.common.dataaccess.exception.IncompatibleRequestException;
import com.raytheon.uf.common.dataaccess.geom.IGeometryRequest;
import com.raytheon.uf.common.time.DataTime;
import com.raytheon.uf.common.time.TimeRange;
import com.raytheon.uf.common.time.util.TimeUtil;
/**
* Utilities for assembling a SQL query based on an IGeometryRequest
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Nov 15, 2012 njensen Initial creation
*
* </pre>
*
* @author njensen
* @version 1.0
*/
public class HydroQueryAssembler {
protected static final String TABLE = "table";
private static final String TIME_COL = "producttime";
private static final String LID_COL = "lid";
/**
* Don't allow instantiation
*/
private HydroQueryAssembler() {
}
/**
* Assembles a SQL query for data
*
* @param request
* the request for data
* @param times
* the times of data to request
* @return the SQL query
*/
public static String assembleGetData(IGeometryRequest request,
DataTime[] times) {
return assembleGetData(request, buildTimeConstraint(times)).toString();
}
/**
* Assembles a SQL query for data
*
* @param request
* the request for data
* @param timeRange
* the time range of data to request
* @return the SQL query
*/
public static String assembleGetData(IGeometryRequest request,
TimeRange timeRange) {
return assembleGetData(request, buildTimeConstraint(timeRange))
.toString();
}
/**
* Assembles a SQL string to query corresponding to the request.
*
* @param request
* the request to transform to SQL
* @param timeConstraint
* a time constraint to apply to the where clause, if applicable.
* May be null.
* @return a SQL string that corresponds to the request
*/
private static CharSequence assembleGetData(IGeometryRequest request,
CharSequence timeConstraint) {
StringBuilder sb = new StringBuilder();
// this method assembles a sql string such as:
// select d.lid, d.productttime, d.value, l.lat, l.lon from height d,
// location l where d.lid = 'ABRN1' and d.lid = l.lid;
// It is requesting the lat/lons of the location every time which
// could potentially be improved in efficiency.
// select
sb.append(buildSelectParams(request));
// from table name
sb.append(buildFromWithLocation(request));
// where
CharSequence where = buildWhere(request, timeConstraint);
if (where.length() > 0) {
sb.append(where);
sb.append(" and d.lid = l.lid");
} else {
sb.append("where d.lid = l.lid");
}
sb.append(buildOrderByTime());
sb.append(";");
// TODO do i need a safety check to limit it, like at 5000 rows or
// something like that?
return sb;
}
/**
* Assembles a SQL query for available times that match the request
*
* @param request
* the request to find available times for
* @return the SQL query
*/
public static String assembleGetTimes(IGeometryRequest request) {
StringBuilder sb = new StringBuilder();
// select
sb.append("select distinct ");
sb.append(TIME_COL);
// from
sb.append(buildFrom(request));
// where
sb.append(buildWhere(request, null));
sb.append(buildOrderByTime());
sb.append(";");
// TODO do i need a safety check to limit it, like at 5000 rows or
// something like that?
return sb.toString();
}
/**
* Assembles a select statement of a query, such as
* "select d.lid, d.producttime, l.lat, l.lon, d.value"
*
* @param request
* the request to form a select statement on
* @return the select statement
*/
private static CharSequence buildSelectParams(IGeometryRequest request) {
StringBuilder sb = new StringBuilder();
// always want the location name and time even if they didn't request it
// so that returned objects will have that information
sb.append("select d.");
sb.append(LID_COL);
sb.append(", d.");
sb.append(TIME_COL);
// request lat and lon for the returned geometry objects
sb.append(", l.lat, l.lon");
// request other columns
for (String param : request.getParameters()) {
sb.append(", d.");
sb.append(param);
}
return sb;
}
/**
* Assembles a from statement, such as "from height"
*
* @param request
* the request to determine the tablename from
* @return the from statement
*/
private static CharSequence buildFrom(IGeometryRequest request) {
return " from " + request.getIdentifiers().get(TABLE);
}
/**
* Assembles a from statement with a location added, such as "from height,
* location l"
*
* @param request
* the request to determine the tablename from
* @return the from statement
*/
private static CharSequence buildFromWithLocation(IGeometryRequest request) {
StringBuilder sb = new StringBuilder();
sb.append(buildFrom(request));
sb.append(" d, location l");
return sb;
}
/**
* Assembles a SQL where clause based on the request
*
* @param request
* the request
* @param timeConstraint
* the time constraint String as produced by
* buildTimeConstraint(), or null
* @return the where clause
*/
private static CharSequence buildWhere(IGeometryRequest request,
CharSequence timeConstraint) {
StringBuilder sb = new StringBuilder();
CharSequence locationConstraint = buildLocationConstraint(request
.getLocationNames());
CharSequence extraConstraints = buildIdentifierConstraint(request
.getIdentifiers());
if (locationConstraint != null || extraConstraints != null
|| timeConstraint != null) {
sb.append(" where ");
if (locationConstraint != null) {
sb.append(locationConstraint);
if (extraConstraints != null || timeConstraint != null) {
sb.append(" and ");
}
}
if (extraConstraints != null) {
sb.append(extraConstraints);
if (timeConstraint != null) {
sb.append(" and ");
}
}
if (timeConstraint != null) {
sb.append(timeConstraint);
}
}
return sb;
}
/**
* Assembles a SQL string that can be used as part of a where clause to
* limit the locations returned.
*
* @param locationNames
* the names of hydro gages to limit on
* @return a SQL string based on lid, such as "lid = 'ABRN1'" or "lid in
* ('ARBN1','ARBN2')"
*/
private static CharSequence buildLocationConstraint(String[] locationNames) {
StringBuilder sb = null;
if (locationNames != null && locationNames.length > 0) {
sb = new StringBuilder();
sb.append(LID_COL);
if (locationNames.length == 1) {
sb.append(" = '");
sb.append(locationNames[0]);
sb.append("'");
} else {
sb.append(" in (");
for (int i = 0; i < locationNames.length; i++) {
sb.append("'");
sb.append(locationNames[i]);
sb.append("'");
if (i < locationNames.length - 1) {
sb.append(",");
}
}
sb.append(")");
}
}
return sb;
}
/**
* Assembles a SQL string that can be used as part of a where clause to
* limit the data returned.
*
* @param identifiers
* the constraints to use
* @return a SQL string based on the map, such as "ts = 'RG'"
*/
private static CharSequence buildIdentifierConstraint(
Map<String, Object> identifiers) {
StringBuilder sb = new StringBuilder();
Map<String, Object> copy = new HashMap<String, Object>(identifiers);
copy.remove(TABLE);
Iterator<Map.Entry<String, Object>> itr = copy.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, Object> entry = itr.next();
String key = entry.getKey();
sb.append(entry.getKey());
Object value = entry.getValue();
if (value instanceof Number) {
sb.append(" = ");
sb.append(value);
} else if (value instanceof String) {
sb.append(" = ");
sb.append("'");
sb.append(value);
sb.append("'");
} else if (value instanceof List) {
sb.append(" in (");
List<?> list = (List<?>) value;
Iterator<?> itrList = list.iterator();
while (itrList.hasNext()) {
sb.append("'");
sb.append(itrList.next().toString());
sb.append("'");
if (itrList.hasNext()) {
sb.append(",");
}
}
sb.append(")");
} else {
throw new IncompatibleRequestException(
"Unable to handle identifier " + key + " of type "
+ value);
}
if (itr.hasNext()) {
sb.append(" and ");
}
}
return ((sb.length() > 0) ? sb : null);
}
/**
* Assembles a SQL string that can be used as part of a where clause to
* limit the data to between the start and end of a time range.
*
* @param timeRange
* the time range to use to make the string
* @return a SQL between statement corresponding to the time range, such as
* "between '2012-09-29 09:00:00' and '2012-09-29 12:00:00'"
*
*/
private static CharSequence buildTimeConstraint(TimeRange timeRange) {
String result = null;
if (timeRange != null) {
result = TIME_COL + " between '"
+ TimeUtil.formatToSqlTimestamp(timeRange.getStart())
+ "' and '"
+ TimeUtil.formatToSqlTimestamp(timeRange.getEnd()) + "'";
}
return result;
}
/**
* Assembles a SQL string that can be used as part of a where clause to
* limit the data to a set of specific times.
*
* @param dataTimes
* the time to limit it to.
* @return a SQL in statement corresponding to the times as strings
*/
private static CharSequence buildTimeConstraint(DataTime[] dataTimes) {
StringBuilder sb = new StringBuilder();
if (dataTimes != null && dataTimes.length > 0) {
sb.append(TIME_COL);
sb.append(" in (");
for (int i = 0; i < dataTimes.length; i++) {
sb.append("'");
sb.append(TimeUtil.formatToSqlTimestamp(dataTimes[i]
.getRefTime()));
sb.append("'");
if (i < dataTimes.length - 1) {
sb.append(",");
}
}
sb.append(")");
}
return ((sb.length() > 0) ? sb : null);
}
/**
* Assembles a SQL string to order results by time, such as
* "order by producttime"
*
* @return a SQL statement that orders results by time
*/
private static CharSequence buildOrderByTime() {
StringBuilder sb = new StringBuilder();
sb.append(" order by ");
sb.append(TIME_COL);
sb.append(" asc");
return sb;
}
}

View file

@ -332,4 +332,11 @@
version="0.0.0"
unpack="false"/>
<plugin
id="com.raytheon.uf.common.dataaccess"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>