Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
decodes.cwms.CwmsTimeSeriesDAO Maven / Gradle / Ivy
Go to download
A collection of software for aggregatting and processing environmental data such as from NOAA GOES satellites.
package decodes.cwms;
import ilex.util.TextUtil;
import ilex.var.NoConversionException;
import ilex.var.TimedVariable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.opendcs.database.ExceptionHelpers;
import org.opendcs.utils.FailableResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import decodes.db.Constants;
import decodes.db.DataType;
import decodes.db.Database;
import decodes.db.EngineeringUnit;
import decodes.db.Site;
import decodes.db.SiteName;
import decodes.db.UnitConverter;
import decodes.sql.DbKey;
import decodes.tsdb.BadTimeSeriesException;
import decodes.tsdb.CTimeSeries;
import decodes.tsdb.DataCollection;
import decodes.tsdb.DbIoException;
import decodes.tsdb.IntervalCodes;
import decodes.tsdb.NoSuchObjectException;
import decodes.tsdb.RecordRangeHandle;
import decodes.tsdb.TasklistRec;
import decodes.tsdb.TimeSeriesIdentifier;
import decodes.tsdb.TsdbDatabaseVersion;
import decodes.tsdb.TsdbException;
import decodes.tsdb.TimeSeriesDb;
import decodes.tsdb.VarFlags;
import decodes.util.DecodesSettings;
import decodes.util.TSUtil;
import opendcs.dai.CompDependsDAI;
import opendcs.dai.DataTypeDAI;
import opendcs.dai.SiteDAI;
import opendcs.dai.TimeSeriesDAI;
import opendcs.dao.DaoBase;
import opendcs.dao.DatabaseConnectionOwner;
import opendcs.dao.DbObjectCache;
import opendcs.util.sql.WrappedConnection;
import usace.cwms.db.dao.ifc.ts.CwmsDbTs;
import usace.cwms.db.dao.util.services.CwmsDbServiceLookup;
public class CwmsTimeSeriesDAO
extends DaoBase
implements TimeSeriesDAI
{
private final Logger log = LoggerFactory.getLogger(CwmsTimeSeriesDAO.class);
protected static final DbObjectCache cache =
new DbObjectCache(60 * 60 * 1000L, false);
protected SiteDAI siteDAO = null;
protected DataTypeDAI dataTypeDAO = null;
private String dbOfficeId = null;
private static boolean noUnitConv = false;
private static long lastCacheReload = 0L;
private String cwmsTsidQueryBase = "SELECT a.CWMS_TS_ID, a.VERSION_FLAG, a.INTERVAL_UTC_OFFSET, "
+ "a.UNIT_ID, a.PARAMETER_ID, '', a.TS_CODE, a.LOCATION_CODE, "
+ "a.LOCATION_ID, a.TS_ACTIVE_FLAG FROM CWMS_V_TS_ID a";
String getMinStmtQuery = null, getTaskListStmtQuery = null;
protected CwmsTimeSeriesDAO(DatabaseConnectionOwner tsdb, String dbOfficeId)
{
super(tsdb, "CwmsTimeSeriesDAO");
siteDAO = tsdb.makeSiteDAO();
dataTypeDAO = tsdb.makeDataTypeDAO();
this.dbOfficeId = dbOfficeId;
}
@Override
public FailableResult findTimeSeriesIdentifier(DbKey key)
{
if (DbKey.isNull(key))
{
return FailableResult.failure(new NoSuchObjectException("Request for TSID with null ts_code"));
}
synchronized(cache)
{
CwmsTsId ret = (CwmsTsId)cache.getByKey(key);
if (ret != null)
{
return FailableResult.success(ret);
}
String q = cwmsTsidQueryBase
+ " WHERE a.TS_CODE = ?";
// Don't need to add DB_OFFICE_ID because TS_CODE is unique.
try
{
final long now = System.currentTimeMillis();
ret = getSingleResult(q, rs ->
{
try
{
CwmsTsId tmp = rs2TsId(rs, true);
tmp.setReadTime(now);
return tmp;
}
catch (Exception ex)
{
throw new SQLException("Unable to process data returned from database.", ex);
}
}, key);
if (ret != null)
{
cache.put(ret);
return FailableResult.success(ret);
}
}
catch(Exception ex)
{
return FailableResult.failure(new DbIoException("Error looking up TS Info for TS_CODE=" + key + ": ", ex));
}
}
return FailableResult.failure(new NoSuchObjectException("No time-series with ts_code=" + key));
}
@Override
public TimeSeriesIdentifier getTimeSeriesIdentifier(DbKey key)
throws DbIoException, NoSuchObjectException
{
FailableResult ret = findTimeSeriesIdentifier(key);
if (ret.isSuccess())
{
return ret.getSuccess();
}
else
{
return ExceptionHelpers.throwDbIoNoSuchObject(ret.getFailure());
}
}
private CwmsTsId rs2TsId(ResultSet rs, boolean createDataType)
throws SQLException, DbIoException, NoSuchObjectException
{
// private String cwmsTsidQueryBase = "SELECT a.CWMS_TS_ID, a.VERSION_FLAG, a.INTERVAL_UTC_OFFSET, "
// + "a.UNIT_ID, a.PARAMETER_ID, '', a.TS_CODE, a.LOCATION_CODE, "
// + "a.LOCATION_ID, a.TS_ACTIVE_FLAG FROM CWMS_V_TS_ID a, CWMS_V_LOC c";
DbKey key = DbKey.createDbKey(rs, 7);
String desc = rs.getString(1);
String param = rs.getString(5);
// String publicSiteName = rs.getString(6);
DataType dt =
DataType.getDataType(Constants.datatype_CWMS, param);
int x = rs.getInt(3);
Integer utcOffset = rs.wasNull() ? null : x;
CwmsTsId ret = new CwmsTsId(key, desc, dt,
desc, TextUtil.str2boolean(rs.getString(2)),
utcOffset, rs.getString(4));
DbKey siteId = DbKey.createDbKey(rs, 8);
Site site = null;
try
{
site = siteDAO.getSiteById(siteId);
}
catch(NoSuchObjectException ex)
{
log.atWarn()
.setCause(ex)
.log("rs2TsId No such Site for TS_ID '{}' with ts_code={} and location_code={}", desc, key, siteId);
throw ex;
}
site.addName(new SiteName(site, Constants.snt_CWMS, rs.getString(9)));
ret.setSite(site);
ret.setSiteDisplayName(site.getPublicName());
ret.setDescription(param + " at " + site.getPublicName());
ret.setActive(TextUtil.str2boolean(rs.getString(10)));
if (decodes.db.Database.getDb().getDbIo().getDatabaseType().equalsIgnoreCase("XML"))
{
return ret;
}
if (createDataType && dt.getId() == Constants.undefinedId)
{
DataType dbdt = null;
try
{
dbdt = dataTypeDAO.lookupDataType(param);
}
catch(NoSuchObjectException ex)
{
dbdt = null;
}
if (dbdt == null || !dbdt.getStandard().equalsIgnoreCase(Constants.datatype_CWMS))
{
dataTypeDAO.writeDataType(dt);
}
else // The datatype already exists, add it to the cache.
{
decodes.db.Database.getDb().dataTypeSet.add(dbdt);
ret.setDataType(dbdt);
}
}
return ret;
}
@Override
public FailableResult findTimeSeriesIdentifier(String uniqueString)
{
int paren = uniqueString.lastIndexOf('(');
String displayName = null;
if (paren > 0 && uniqueString.trim().endsWith(")"))
{
displayName = uniqueString.substring(paren+1);
uniqueString = uniqueString.substring(0, paren);
int endParen = displayName.indexOf(')');
if (endParen > 0)
{
displayName = displayName.substring(0, endParen);
}
}
TimeSeriesIdentifier ret = null;
synchronized(cache)
{
ret = cache.getByUniqueName(uniqueString);
}
if (ret != null)
{
if (displayName != null)
{
ret.setDisplayName(displayName);
}
return FailableResult.success(ret);
}
else if (ret == null && lastCacheReloadWithin(1, TimeUnit.HOURS))
{
return FailableResult.failure(new NoSuchObjectException("No TimeSeries in fresh cache."));
}
DbKey ts_code = ts_id2ts_code(uniqueString);
if (ts_code == Constants.undefinedId)
{
return FailableResult.failure(
new NoSuchObjectException("No timeseries with name '"+uniqueString+"' is defined in this database.")
);
}
FailableResult tmp = findTimeSeriesIdentifier(ts_code);
if (tmp.isSuccess())
{
if (displayName != null)
{
tmp.getSuccess().setDisplayName(displayName);
}
}
return tmp;
}
/**
* Given a 6-part time-series identifier, return the unique ts_code.
* Implicit filtering by office ID is also done.
* @param tsid location.parm.parmtype.interval.duration.version
* @return ts_code surrogate key, or Constants.undefinedId if not found.
*/
private DbKey ts_id2ts_code(String tsid)
{
String q = "SELECT TS_CODE FROM CWMS_V_TS_ID "
+ "WHERE upper(CWMS_TS_ID) = upper(?)";
ArrayList parameters = new ArrayList<>();
parameters.add(tsid);
if (db.getTsdbVersion() >= TsdbDatabaseVersion.VERSION_8)
{
q = q + " AND upper(DB_OFFICE_ID) = upper(?)";
parameters.add(dbOfficeId);
}
try
{
return getSingleResultOr(q, rs -> DbKey.createDbKey(rs, "ts_code"),
Constants.undefinedId, parameters.toArray(new Object[0]));
}
catch(Exception ex)
{
log.atWarn()
.setCause(ex)
.log("Error looking up TS_CODE for TS_ID '{}'", tsid);
}
return Constants.undefinedId;
}
private static boolean lastCacheReloadWithin(long time, TimeUnit units)
{
final long within = units.toMillis(time);
return (System.currentTimeMillis() - lastCacheReload) < within;
}
@Override
public void close()
{
dataTypeDAO.close();
siteDAO.close();
super.close();
}
@Override
public int fillTimeSeries( CTimeSeries ts, Date from, Date until )
throws DbIoException, BadTimeSeriesException
{
return fillTimeSeries(ts, from, until, true, true, true);
}
@Override
public int fillTimeSeries(CTimeSeries cts, Date from, Date until,
boolean include_lower, boolean include_upper, boolean overwriteExisting)
throws DbIoException, BadTimeSeriesException
{
String lower_check = include_lower ? " >= " : " > ";
String upper_check = include_upper ? " <= " : " < ";
DbKey ts_code = cts.getSDI();
if (!cts.isExpanded())
{
fillTimeSeriesMetadata(cts); // may throw BadTimeSeriesException
cts.setIsExpanded();
}
StringBuffer q = new StringBuffer();
ArrayList parameters = new ArrayList<>();
q.append("SELECT DATE_TIME, ROUND(VALUE,8), QUALITY_CODE FROM CWMS_V_TSV "
+ " WHERE TS_CODE = ?");
parameters.add(ts_code);
TimeSeriesIdentifier tsid = cts.getTimeSeriesIdentifier();
if (tsid == null)
{
try
{
tsid = getTimeSeriesIdentifier(ts_code);
cts.setTimeSeriesIdentifier(tsid);
}
catch(NoSuchObjectException ex)
{
log.warn("unable to fillTimeSeries",ex);
}
}
// Part of the contract is to honor the units already specified
// in the CTimeSeries.
UnitConverter unitConverter = db.makeUnitConverterForRead(cts);
if (from != null)
{
q.append(" AND DATE_TIME " + lower_check + " ?");
parameters.add(from);
}
if (until != null)
{
q.append(" AND DATE_TIME " + upper_check + " ?");
parameters.add(until);
}
q.append(" AND VALUE IS NOT INFINITE");
try
{
int numAdded[] = new int[1];
numAdded[0] = 0;
doQuery(q.toString(), rs ->
{
TimedVariable tv = null;
try
{
tv = rs2TimedVariable(rs);
if (tv == null)
{
return;
}
}
catch(SQLException ex)
{
log.atWarn()
.setCause(ex)
.log("Error getting data for time series='{}'",cts.getNameString());
return;
}
// For computation processor, we never want to overwrite data
// we already have. For a report generator, we DO.
Date d = tv.getTime();
if (!overwriteExisting && cts.findWithin(d.getTime()/1000L, 10) != null)
{
return;
}
if (tv != null)
{
if (unitConverter != null)
{
try
{
tv.setValue(unitConverter.convert(tv.getDoubleValue()));
}
catch (Exception ex)
{
log.warn("unable to fillTimeSeries: ", ex);
}
}
cts.addSample(tv);
numAdded[0]++;
}
},
parameters.toArray(new Object[0]));
return numAdded[0];
}
catch(SQLException ex)
{
String msg = "Error getting data for time series=%s";
throw new DbIoException(String.format(msg,cts.getNameString()), ex);
}
}
@Override
public int fillTimeSeries(CTimeSeries cts, Collection queryTimes)
throws DbIoException, BadTimeSeriesException
{
if (!cts.isExpanded())
{
fillTimeSeriesMetadata(cts); // may throw BadTimeSeriesException
cts.setIsExpanded();
}
// Part of the contract is to honor the units already specified
// in the CTimeSeries.
UnitConverter unitConverter = db.makeUnitConverterForRead(cts);
String qbase = "SELECT DATE_TIME, ROUND(VALUE,8), QUALITY_CODE FROM CWMS_V_TSV "
+ " WHERE TS_CODE = " + cts.getSDI()
+ " and DATE_TIME IN (";
int datesPerQuery = 300;
int start = 0;
int end = 0;
int numAdded[] = new int[1];
numAdded[0] = 0;
StringBuilder sb = new StringBuilder();
int size = queryTimes.size();
Date times[] = new Date[size];
times = queryTimes.toArray(times);
while (end < size)
{
sb.append(db.sqlDate(times[end]));
end++;
if (end - start >= datesPerQuery || end == size)
{
sb.append(")");
sb.append(" AND VALUE IS NOT INFINITE");
String q = qbase + sb.toString();
sb.setLength(0);
start = end;
try
{
doQuery(q, rs ->
{
TimedVariable tv = null;
try
{
tv = rs2TimedVariable(rs);
if (tv == null)
{
return;
}
}
catch(SQLException ex)
{
String msg= "Error reading data with query '{}'";
log.atWarn()
.setCause(ex)
.log(msg, q);
return;
}
if (tv != null)
{
// For computation processor, we never want to overwrite data
// we already have.
Date d = tv.getTime();
if (cts.findWithin(d.getTime()/1000L, 10) != null)
{
return;
}
if (unitConverter != null)
{
try
{
tv.setValue(unitConverter.convert(tv.getDoubleValue()));
}
catch (Exception ex)
{
log.warn("unable to set value.", ex);
}
}
cts.addSample(tv);
numAdded[0]++;
}
});
}
catch(SQLException ex)
{
String msg= "Error reading data with query '{}'";
log.atWarn()
.setCause(ex)
.log(msg, q);
}
}
else if (end < size)
{
sb.append(", ");
}
}
return numAdded[0];
}
@Override
public TimedVariable getPreviousValue(CTimeSeries cts, Date refTime)
throws DbIoException, BadTimeSeriesException
{
if (!cts.isExpanded())
{
fillTimeSeriesMetadata(cts); // may throw BadTimeSeriesException
cts.setIsExpanded();
}
// Part of the contract is to honor the units already specified
// in the CTimeSeries.
UnitConverter unitConverter = db.makeUnitConverterForRead(cts);
String q = "SELECT DATE_TIME, ROUND(VALUE,8), QUALITY_CODE FROM CWMS_V_TSV "
+ " WHERE TS_CODE = ?"
+ " and DATE_TIME = "
+ "(select max(date_time) from CWMS_V_TSV "
+ "where TS_CODE = ?"
+ " and date_time < ?"
+ " AND VALUE IS NOT NULL "
+ " AND BITAND(QUALITY_CODE, ?) = 0 "
+ ")"
+ " AND VALUE IS NOT INFINITE";
ArrayList parameters = new ArrayList<>();
parameters.add(cts.getSDI());
parameters.add(cts.getSDI());
parameters.add(refTime);
parameters.add(CwmsFlags.QC_MISSING_OR_REJECTED);
try
{
return getSingleResult(q, rs ->
{
TimedVariable tv = rs2TimedVariable(rs);
if (tv != null)
{
if (unitConverter != null)
{
try
{
tv.setValue(unitConverter.convert(tv.getDoubleValue()));
}
catch (Exception ex)
{
log.warn("getPreviousValue: Error in unit conversion: ", ex);
}
}
cts.addSample(tv);
}
return tv;
},
parameters.toArray(new Object[0]));
}
catch(SQLException ex)
{
String msg= "Error reading data with query '{}'";
throw new DbIoException(String.format(msg,q), ex);
}
}
@Override
public TimedVariable getNextValue(CTimeSeries cts, Date refTime)
throws DbIoException, BadTimeSeriesException
{
if (!cts.isExpanded())
{
fillTimeSeriesMetadata(cts); // may throw BadTimeSeriesException
cts.setIsExpanded();
}
// Part of the contract is to honor the units already specified
// in the CTimeSeries.
UnitConverter unitConverter = db.makeUnitConverterForRead(cts);
String q = "SELECT DATE_TIME, ROUND(VALUE,8), QUALITY_CODE FROM CWMS_V_TSV "
+ " WHERE TS_CODE = ?"
+ " and DATE_TIME = "
+ "(select min(date_time) from CWMS_V_TSV "
+ "where TS_CODE = ?"
+ " and date_time > ?"
+ " AND VALUE IS NOT NULL "
+ " AND BITAND(QUALITY_CODE, ?) = 0 "
+ ")"
+ " AND VALUE IS NOT INFINITE";
ArrayList parameters = new ArrayList<>();
parameters.add(cts.getSDI());
parameters.add(cts.getSDI());
parameters.add(refTime);
parameters.add(CwmsFlags.QC_MISSING_OR_REJECTED);
try
{
return getSingleResult(q, rs ->
{
TimedVariable tv = rs2TimedVariable(rs);
if (tv != null)
{
if (unitConverter != null)
{
try
{
tv.setValue(unitConverter.convert(tv.getDoubleValue()));
}
catch (Exception ex)
{
log.warn("getNextValue: Error in unit conversion: ", ex);
}
}
cts.addSample(tv);
}
return tv;
},
parameters.toArray(new Object[0]));
}
catch(SQLException ex)
{
String msg= "Error reading data with query '%s'";
throw new DbIoException(String.format(msg,q), ex);
}
}
@Override
public void saveTimeSeries(CTimeSeries ts) throws DbIoException,
BadTimeSeriesException
{
// NOTE: For CWMS, we don't need separate methods for write/delete
// because a deleted value is just one with a null value and the
// VALIDITY mask set to MISSING.
TimeSeriesIdentifier tsId = ts.getTimeSeriesIdentifier();
try
{
if (tsId == null)
{
tsId = getTimeSeriesIdentifier(ts.getSDI());
}
}
catch(NoSuchObjectException ex)
{
throw new BadTimeSeriesException("Could not find timeseries: " + ts.getDisplayName(), ex);
}
String path = tsId.getUniqueString();
String unitsAbbr = ts.getUnitsAbbr();
if (unitsAbbr == null || unitsAbbr.trim().length() == 0
|| unitsAbbr.equalsIgnoreCase("unknown"))
{
fillTimeSeriesMetadata(ts);
unitsAbbr = ts.getUnitsAbbr();
}
// noUnitConv is used for testing by TsImport only
if (!noUnitConv)
{
// Convert to the required 'storage units'.
log.trace("Time Series '{}' have units '{}' require units '{}'",
tsId.getUniqueString(), unitsAbbr, tsId.getStorageUnits());
if (!unitsAbbr.equalsIgnoreCase(tsId.getStorageUnits()))
{
TSUtil.convertUnits(ts, tsId.getStorageUnits());
unitsAbbr = tsId.getStorageUnits();
}
}
else
{
log.trace("Will write time series {} with unit '{}'",
tsId.getUniqueString(), unitsAbbr);
}
// We do not yet support versioned data
java.sql.Timestamp versionDate = null;
// We will always use overrideProtection false
boolean overrideProtection = false;
// We use the RMA Java interface to write to DB
try (Connection conn = getConnection())
{
CwmsDbTs cwmsDbTs = CwmsDbServiceLookup.buildCwmsDb(CwmsDbTs.class, conn);
ArrayList msecArray = new ArrayList();
ArrayList valueArray = new ArrayList();
ArrayList qualArray = new ArrayList();
// Handle the normal WRITE/DELETE values.
fillTsArrays(ts, msecArray, valueArray, qualArray, false);
int num2write = msecArray.size();
if (num2write > 0)
{
long[] times = new long[num2write];
double[] values = new double[num2write];
int[] qualities = new int[num2write];
for(int idx = 0; idx 0)
{
long[] times = new long[num2write];
double[] values = new double[num2write];
int[] qualities = new int[num2write];
for(int idx = 0; idx msecArray, ArrayList valueArray,
ArrayList qualArray, boolean noOverwrite)
{
msecArray.clear();
valueArray.clear();
qualArray.clear();
for(int i=0; i use to delete specific dates
true, // use latest version if version date is null (which it is)
1, // item mask. 1 means delete values only
"F"); // Override Protection, F=do not delete protected data.
}
catch(SQLException ex)
{
log.atWarn()
.setCause(ex)
.log("deleteTimeSeriesRange - Error in CwmsDbTs.deleteTs for tsid '{}' from {} to {}.",
tsid, sdf.format(from), sdf.format(until));
}
// // For CWMS Comps, there are no physical deletes, so first we read, then
// // we set the MISSING flag, then we store.
// int n = fillTimeSeries(cts, from, until, true, true, true);
// if (n == 0)
// return;
//
// int sz = cts.size();
// int num2delete = 0;
// for(int i=0; i= 0 && d.compareTo(until) <= 0)
// {
// VarFlags.setToDelete(tv);
// num2delete++;
// }
// }
//
// if (num2delete > 0)
// saveTimeSeries(cts);
}
@Override
public void fillTimeSeriesMetadata(CTimeSeries ts) throws DbIoException,
BadTimeSeriesException
{
log.trace("fillTimeSeriesMetadata sdi={}", ts.getSDI());
try
{
DbKey ts_code = ts.getSDI();
CwmsTsId tsid = (CwmsTsId)ts.getTimeSeriesIdentifier();
if (tsid == null)
{
tsid = (CwmsTsId)getTimeSeriesIdentifier(ts_code);
ts.setTimeSeriesIdentifier(tsid);
}
ts.setInterval(tsid.getInterval());
ts.setBriefDescription(tsid.getDataType().getCode() + " @ "
+ tsid.getSiteName());
// Set table selector to ParamType.Duration.Version so it will match
ts.setTableSelector(tsid.getParamType() + "." +
tsid.getDuration() + "." + tsid.getVersion());
String existingUnits = ts.getUnitsAbbr();
if (existingUnits == null || existingUnits.isEmpty() || existingUnits.equalsIgnoreCase("unknown"))
{
ts.setUnitsAbbr(tsid.getStorageUnits());
}
}
catch(NoSuchObjectException ex)
{
log.warn("Error expanding SDI: ", ex);
ts.setDisplayName("unknownSite:unknownType-unknownIntv");
ts.setUnitsAbbr("EU??");
}
}
/**
* Pass ResultSet containing DATE_TIME, VALUE, QUALITY_CODE.
* @return TimedVariable or null if this value flagged as missing.
*/
private TimedVariable rs2TimedVariable(ResultSet rs)
throws SQLException
{
Date timeStamp = db.getFullDate(rs, 1);
double value = rs.getDouble(2);
// Check for missing, deleted, or rejected data
if (rs.wasNull())
{
return null;
}
long lf = rs.getLong(3);
int f = CwmsFlags.cwmsQuality2flag(lf);
if ((f & CwmsFlags.VALIDITY_MISSING) != 0)
{
return null;
}
TimedVariable tv = new TimedVariable(value);
tv.setTime(timeStamp);
tv.setFlags(f);
return tv;
}
@Override
public void deleteTimeSeries(TimeSeriesIdentifier tsid)
throws DbIoException
{
// Note CWMS with db version 7 -- the cp_comp_depends table is updated
// by Gang's message-queue handler.
if (db.getTsdbVersion() < TsdbDatabaseVersion.VERSION_7)
{
try (CompDependsDAI compDependsDAO = db.makeCompDependsDAO();)
{
compDependsDAO.removeTsDependencies(tsid);
}
}
try (Connection conn = getConnection();)
{
CwmsDbTs cwmsDbTs = CwmsDbServiceLookup.buildCwmsDb(CwmsDbTs.class, conn);
log.debug("Deleting TSID '{}' from office ID={}", tsid.getUniqueString(), dbOfficeId);
cwmsDbTs.deleteAll(conn, dbOfficeId, tsid.getUniqueString());
synchronized(cache)
{
cache.remove(tsid.getKey());
}
refreshTsView();
}
catch(Exception ex)
{
String msg = "Cannot delete time series '{}'";
log.atWarn()
.setCause(ex)
.log(msg, tsid.getUniqueString());
}
}
@Override
public CTimeSeries makeTimeSeries(TimeSeriesIdentifier tsid)
throws DbIoException, NoSuchObjectException
{
DbKey sdi = tsid.getKey();
CTimeSeries ret = new CTimeSeries(sdi, tsid.getInterval(),
tsid.getTableSelector());
ret.setTimeSeriesIdentifier(tsid);
ret.setDisplayName(tsid.getDisplayName());
return ret;
}
@Override
public ArrayList listTimeSeries()
throws DbIoException
{
// MJM 20161025 don't reload more if already done within threshold.
if (System.currentTimeMillis() - lastCacheReload > cacheReloadMS)
{
reloadTsIdCache();
}
ArrayList ret = new ArrayList();
synchronized(cache)
{
for (Iterator tsidit = cache.iterator(); tsidit.hasNext();)
{
ret.add(tsidit.next());
}
}
return ret;
}
@Override
public ArrayList listTimeSeries(boolean forceRefresh)
throws DbIoException
{
if (forceRefresh)
{
lastCacheReload = 0L;
}
return listTimeSeries();
}
@Override
public void reloadTsIdCache() throws DbIoException
{
log.debug("reloadTsIdCache()");
// Each TSID will need a site, so prefill the site cache to prevent
// it from doing individual reads for each site.
// siteDAO.fillCache();
String q = cwmsTsidQueryBase + " WHERE upper(a.DB_OFFICE_ID) = upper(?)";
int origFetchSize = getFetchSize();
try
{
int tsidFetchSize = DecodesSettings.instance().tsidFetchSize;
if (tsidFetchSize > 0)
{
setFetchSize(tsidFetchSize);
}
List tsidList = getResultsIgnoringNull(q, rs ->
{
CwmsTsId retVal = null;
try
{
retVal = rs2TsId(rs, false);
}
catch (DbIoException | NoSuchObjectException ex)
{
log.atWarn()
.setCause(ex)
.log("Error creating Cwms TSID -- skipped.");
}
return retVal;
}, dbOfficeId);
synchronized(cache)
{
cache.clear();
for(TimeSeriesIdentifier tsid: tsidList)
{
cache.put(tsid);
}
}
log.debug("After fill, cache has {} TSIDs.", cache.size());
}
catch (SQLException ex)
{
final String msg = "Failed to reload CwmsTimeSeriesDb TsId Cache.";
log.atError()
.setCause(ex)
.log(msg);
throw new DbIoException(msg, ex);
}
finally
{
setFetchSize(origFetchSize);
}
lastCacheReload = System.currentTimeMillis();
}
@Override
public DbObjectCache getCache()
{
return cache;
}
@Override
public DbKey createTimeSeries(TimeSeriesIdentifier tsid)
throws DbIoException, NoSuchObjectException, BadTimeSeriesException
{
tsid.checkValid();
Site site = tsid.getSite();
if (site == null)
{
String sn = tsid.getSiteName();
if (sn == null || sn.trim().length() == 0)
{
throw new BadTimeSeriesException("TSID '" + tsid.getUniqueString()
+ "' has no location part.");
}
DbKey siteID = siteDAO.lookupSiteID(sn);
if (DbKey.isNull(siteID))
{
throw new NoSuchObjectException("TSID '" + tsid.getUniqueString()
+ "' cannot find location '" + sn + "' in this database.");
}
}
String path = tsid.getUniqueString();
try (Connection conn = getConnection();)
{
CwmsDbTs cwmsDbTs = CwmsDbServiceLookup.buildCwmsDb(CwmsDbTs.class, conn);
int utcOffset =
IntervalCodes.getIntervalSeconds(tsid.getInterval()) == 0 ?
HecConstants.NO_UTC_OFFSET : HecConstants.UNDEFINED_UTC_OFFSET;
DbKey tsKey = Constants.undefinedId;
BigInteger tsCode = cwmsDbTs.createTsCodeBigInteger(
conn,
dbOfficeId,
path, // 6-part path name
utcOffset, // utcOfficeMinutes
null, // intervalForward
null, // intervalBackward
false, // versionFlag
true); // active
tsKey = DbKey.createDbKey(tsCode.longValue());
tsid.setKey(tsKey);
refreshTsView();
return tsKey;
}
catch(SQLException ex)
{
// CWMS-5773 If the create error is due to some kind of user-level constraint,
// then throw NoSuchObject, meaning that the tsid is bad but the connection is still
// Ok. Prasad says ORA numbers > 20000 mean user-defined. Probably some kind of
// constraint violation.
String exs = ex.toString();
int oraidx = exs.indexOf("ORA-");
if (oraidx >= 0)
{
exs = exs.substring(oraidx+4);
int intlen = 0;
for(; intlen < exs.length() && Character.isDigit(exs.charAt(intlen)); intlen++);
if (intlen > 0)
{
try
{
int oraerr = Integer.parseInt(exs.substring(0, intlen));
if (oraerr >= 20000)
{
throw new NoSuchObjectException(
"Error creating time series for '" + path + "' with officeId '"
+ dbOfficeId + "': " + ex, ex);
}
}
catch(NumberFormatException ex2) { /* fall through & throw DbIoException */ }
}
}
// This must be more serious. Assume db connection is now hosed.
throw new DbIoException(
"Error creating time series for '" + path + "' with officeId '"
+ dbOfficeId, ex);
}
}
private void refreshTsView()
{
if (db.getTsdbVersion() >= TsdbDatabaseVersion.VERSION_9)
{
return;
}
// MJM 1/12/2011 After we delete a time-series we must explicitly
// refresh the CWMS_V_TS_ID view, otherwise it can take up to 5 min.
try
{
log.warn("calling legacy database method: 'cwms_util.refresh_mv_cwms_ts_id()'");
doModify("call cwms_util.refresh_mv_cwms_ts_id()", new Object[0]);
}
catch(Exception ex)
{
log.atWarn()
.setCause(ex)
.log("Error in cwms_util.refresh_mv_cwms_ts_id.");
}
}
public static void setNoUnitConv(boolean noUnitConv)
{
CwmsTimeSeriesDAO.noUnitConv = noUnitConv;
}
@Override
public void setAppModule(String module)
{
// Do nothing. CWMS doesn't use modules.
}
@Override
public void modifyTSID(TimeSeriesIdentifier tsid)
throws DbIoException, NoSuchObjectException, BadTimeSeriesException
{
// Not implemented for CWMS
}
@Override
public DataCollection getNewData(DbKey applicationId)
throws DbIoException
{
DataCollection dataCollection = new DataCollection();
String failTimeClause =
DecodesSettings.instance().retryFailedComputations
? " and (a.FAIL_TIME is null OR SYSDATE - a.FAIL_TIME >= 1/24)"
: "";
getTaskListStmtQuery =
"select a.RECORD_NUM, a.SITE_DATATYPE_ID, a.VALUE, a.START_DATE_TIME, "
+ "a.DELETE_FLAG, a.UNIT_ID, a.VERSION_DATE, a.QUALITY_CODE, a.MODEL_RUN_ID "
+ "from CP_COMP_TASKLIST a "
+ "where a.LOADING_APPLICATION_ID = ?"
+ " and ROWNUM < 20000"
+ failTimeClause
+ " ORDER BY a.site_datatype_id, a.start_date_time";
try (Connection conn = getConnection();
PreparedStatement getTaskListStmt = conn.prepareStatement(getTaskListStmtQuery);
)
{
getTaskListStmt.setLong(1,applicationId.getValue());
ArrayList tasklistRecs = new ArrayList();
ArrayList badRecs = new ArrayList();
log.trace("Executing '{}' with appId={}", getTaskListStmtQuery, applicationId);
try (ResultSet rs = getTaskListStmt.executeQuery())
{
Date lastTimestamp = null;
while (rs.next())
{
// Extract the info needed from the result set row.
Date timeStamp = new Date(rs.getDate(4).getTime());
boolean exceedsMaxTimeGap = exceedsMaxTimeGap(lastTimestamp, timeStamp);
if(exceedsMaxTimeGap)
{
break;
}
lastTimestamp = timeStamp;
int recordNum = rs.getInt(1);
DbKey sdi = DbKey.createDbKey(rs, 2);
double value = rs.getDouble(3);
boolean valueWasNull = rs.wasNull();
String df = rs.getString(5);
char c = df.toLowerCase().charAt(0);
boolean deleted = false;
if (c == 'u')
{
// msg handler will send this when he gets
// TsCodeChanged. It tells me to update my cache.
DbObjectCache cache = getCache();
synchronized(cache)
{
TimeSeriesIdentifier tsid = cache.getByKey(sdi);
if (tsid != null)
{
DbKey newCode = DbKey.createDbKey(rs, 9);
cache.remove(sdi);
tsid.setKey(newCode);
cache.put(tsid);
continue;
}
}
}
else
{
deleted = TextUtil.str2boolean(df);
}
String unitsAbbr = rs.getString(6);
Date versionDate = db.getFullDate(rs, 7);
BigDecimal qc = rs.getBigDecimal(8);
long qualityCode = qc == null ? 0 : qc.longValue();
TasklistRec rec = new TasklistRec(recordNum, sdi, value,
valueWasNull, timeStamp, deleted,
unitsAbbr, versionDate, qualityCode);
tasklistRecs.add(rec);
}
RecordRangeHandle rrhandle = new RecordRangeHandle(applicationId);
// Process the real-time records collected above.
for(TasklistRec rec : tasklistRecs)
{
processTasklistEntry(rec, dataCollection, rrhandle, badRecs, applicationId);
}
dataCollection.setTasklistHandle(rrhandle);
// Delete the bad tasklist recs, 250 at a time.
if (badRecs.size() > 0)
{
log.debug("getNewDataSince deleting {} bad tasklist records.", + badRecs.size());
doModifyBatch("delete from cp_comp_tasklist where record_num = ?",
(v) -> new Object[] {v},
badRecs, 250);
badRecs.clear();
}
// Show each tasklist entry in the log if we're at debug level 3
if (log.isTraceEnabled())
{
List allTs = dataCollection.getAllTimeSeries();
log.trace("getNewData, returning {} TimeSeries.", allTs.size());
for(CTimeSeries ts : allTs)
{
log.trace("ts '{}' {} values.", ts.getTimeSeriesIdentifier().getUniqueString(), ts.size());
}
}
return dataCollection;
}
}
catch(SQLException ex)
{
log.atWarn()
.setCause(ex)
.log("Error reading new data.");
throw new DbIoException("Error reading new data: " + ex.getLocalizedMessage(), ex);
}
}
final boolean exceedsMaxTimeGap(Date lastTimestamp, Date currentTimestamp)
{
if(lastTimestamp == null || currentTimestamp == null)
{
return false;
}
long daysBetween = TimeUnit.MILLISECONDS.toDays(currentTimestamp.getTime() - lastTimestamp.getTime());
return daysBetween >= DecodesSettings.instance().cp_cwmstsdb_getNewData_max_timegap_days;
}
private void processTasklistEntry(TasklistRec rec,
DataCollection dataCollection, RecordRangeHandle rrhandle,
ArrayList badRecs, DbKey applicationId)
throws DbIoException
{
// Find time series if already in data collection.
// If not construct one and add it.
CTimeSeries cts = dataCollection.getTimeSeriesByUniqueSdi(rec.getSdi());
if (cts == null)
{
try
{
TimeSeriesIdentifier tsid = getTimeSeriesIdentifier(rec.getSdi());
String tabsel = tsid.getPart("paramtype") + "." +
tsid.getPart("duration") + "." + tsid.getPart("version");
cts = new CTimeSeries(rec.getSdi(), tsid.getInterval(),
tabsel);
cts.setModelRunId(-1);
cts.setTimeSeriesIdentifier(tsid);
cts.setUnitsAbbr(rec.getUnitsAbbr());
if (((TimeSeriesDb)db).fillDependentCompIds(cts, applicationId, this) == 0)
{
log.warn("Deleting tasklist rec for '{}' because no dependent comps.", tsid.getUniqueString());
if (badRecs != null)
{
badRecs.add(rec.getRecordNum());
}
return;
}
dataCollection.addTimeSeries(cts);
}
catch(decodes.tsdb.DuplicateTimeSeriesException ex)
{ // won't happen -- already verified it's not there.
log.trace("An exception has been thrown that shouldn't have been. Please inform the project.", ex);
}
catch(NoSuchObjectException ex)
{
log.warn("Deleting tasklist rec for non-existent ts_code {}", rec.getSdi());
if (badRecs != null)
{
badRecs.add(rec.getRecordNum());
}
return;
}
}
else
{
// The time series already existed from a previous tasklist rec in this run.
// Make sure this rec's unitsAbbr matches the CTimeSeries.getUnitsAbbr().
// If not, convert it to the CTS units.
String recUnitsAbbr = rec.getUnitsAbbr();
String ctsUnitsAbbr = cts.getUnitsAbbr();
if (ctsUnitsAbbr == null) // no units yet assigned?
{
cts.setUnitsAbbr(ctsUnitsAbbr = recUnitsAbbr);
}
else if (recUnitsAbbr == null) // for some reason, this tasklist record doesn't have units
{
recUnitsAbbr = ctsUnitsAbbr;
}
else if (!TextUtil.strEqualIgnoreCase(recUnitsAbbr, ctsUnitsAbbr))
{
EngineeringUnit euOld = EngineeringUnit.getEngineeringUnit(recUnitsAbbr);
EngineeringUnit euNew = EngineeringUnit.getEngineeringUnit(ctsUnitsAbbr);
UnitConverter converter = Database.getDb().unitConverterSet.get(euOld, euNew);
if (converter != null)
{
try
{
rec.setValue(converter.convert(rec.getValue()));
}
catch (Exception ex)
{
log.atWarn()
.setCause(ex)
.log(
"Tasklist for '{}' exception converting {} to {} -- will use as-is.",
cts.getTimeSeriesIdentifier().getUniqueString(), rec.getValue(), rec.getUnitsAbbr());
}
}
else
{
log.warn("Tasklist for '{}' cannot convert {} {} to {}",
cts.getTimeSeriesIdentifier().getUniqueString(), rec.getValue(), rec.getUnitsAbbr(), cts.getUnitsAbbr());
}
}
}
if (rrhandle != null)
{
rrhandle.addRecNum(rec.getRecordNum());
}
// Construct timed variable with appropriate flags & add it.
TimedVariable tv = new TimedVariable(rec.getValue());
tv.setTime(rec.getTimeStamp());
tv.setFlags(CwmsFlags.cwmsQuality2flag(rec.getQualityCode()));
if (!rec.isDeleted() && !rec.isValueWasNull())
{
VarFlags.setWasAdded(tv);
cts.addSample(tv);
// Remember which tasklist records are in this timeseries.
cts.addTaskListRecNum(rec.getRecordNum());
log.trace("Added value {} to time series '{}' flags=0x{} cwms qualcode=0x{}",
tv.toString(), cts.getTimeSeriesIdentifier().getUniqueString(),
Integer.toHexString(tv.getFlags()), Long.toHexString(rec.getQualityCode()));
}
else
{
VarFlags.setWasDeleted(tv);
log.warn("Discarding deleted value {} for time series '{}' flags=0x{} cwms qualcode=0x{}",
tv.toString(), cts.getTimeSeriesIdentifier().getUniqueString(),
Integer.toHexString(tv.getFlags()), Long.toHexString(rec.getQualityCode()));
}
}
@Override
protected Connection getConnection()
{
// local getConnection() method that saves the connection locally
if (myCon == null)
{
myCon = db.getConnection();
}
siteDAO.setManualConnection(myCon);
dataTypeDAO.setManualConnection(myCon);
// NOTE: there should already be a pooled connection used by the
// CWMS Components. This is primarily to cover the cases we haven't gotten to yet.
return new WrappedConnection(myCon, rs -> {},true);
}
@Override
public TimeSeriesIdentifier getTimeSeriesIdentifier(String uniqueString) throws DbIoException, NoSuchObjectException {
FailableResult ts = findTimeSeriesIdentifier(uniqueString);
if (ts.isSuccess())
{
return ts.getSuccess();
}
else
{
return ExceptionHelpers.throwDbIoNoSuchObject(ts.getFailure());
}
}
}