Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 45 additions & 56 deletions cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@
import static org.jooq.SQLDialect.ORACLE;

import com.google.common.flogger.FluentLogger;
import com.google.common.flogger.StackSize;
import cwms.cda.ApiServlet;
import cwms.cda.api.errors.AlreadyExists;
import cwms.cda.api.errors.FieldLengthExceededException;
import cwms.cda.api.errors.InvalidItemException;
import cwms.cda.api.errors.NotFoundException;
import cwms.cda.datasource.ConnectionPreparingDataSource;
import cwms.cda.datasource.DelegatingConnectionPreparer;
import cwms.cda.helpers.DatabaseHelpers.SCHEMA_VERSION;
import cwms.cda.security.CwmsAuthException;
import io.javalin.http.Context;
Expand Down Expand Up @@ -72,9 +72,7 @@
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.owasp.html.HtmlPolicyBuilder;
import org.owasp.html.PolicyFactory;
import usace.cwms.db.jooq.codegen.packages.CWMS_ENV_PACKAGE;
import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE;
import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE;


public abstract class JooqDao<T> extends Dao<T> {
Expand All @@ -89,7 +87,7 @@ public abstract class JooqDao<T> extends Dao<T> {

static ExecuteListener listener = new ExceptionWrappingListener();
private static final Pattern INVALID_OFFICE_ID = Pattern.compile(
"INVALID_OFFICE_ID: \"([^\"]+)\" is not a valid CWMS office id");
"INVALID_OFFICE_ID: \"([^\"]+)\" is not a valid CWMS office id");
private static final Pattern INVALID_UNIT = Pattern.compile(
"(.+\\R+){6}ORA-20102: The unit: \\S+"
+ " is not a recognized CWMS Database unit for the .+(.+\\R+){10}");
Expand Down Expand Up @@ -135,34 +133,22 @@ protected JooqDao(DSLContext dsl) {
*/
public static DSLContext getDslContext(Context ctx) {
DSLContext retVal;
final String officeId = ctx.attribute(ApiServlet.OFFICE_ID);

final DataSource dataSource = ctx.attribute(ApiServlet.DATA_SOURCE);
final Boolean isNewLRTS = ctx.header(ApiServlet.IS_NEW_LRTS) == null
? null : Boolean.parseBoolean(ctx.header(ApiServlet.IS_NEW_LRTS));
final boolean isNewLRTS = ctx.header(ApiServlet.IS_NEW_LRTS) != null && Boolean.parseBoolean(ctx.header(ApiServlet.IS_NEW_LRTS));

if (dataSource != null) {
DataSource wrappedDataSource = new ConnectionPreparingDataSource(connection ->
setClientInfo(ctx, connection), dataSource);
retVal = DSL.using(wrappedDataSource, SQLDialect.ORACLE18C);
} else {
// Some tests still use this method
logger.atFine().withStackTrace(StackSize.FULL)
.log("System still using old context method.");
Connection database = ctx.attribute(ApiServlet.DATABASE);
retVal = getDslContext(database, officeId);
}
DelegatingConnectionPreparer preparer = new DelegatingConnectionPreparer(
connection -> setClientInfo(ctx, connection),
new cwms.cda.datasource.LrtsSessionPreparer(isNewLRTS));
DataSource wrappedDataSource = new ConnectionPreparingDataSource(preparer, dataSource);
retVal = DSL.using(wrappedDataSource, SQLDialect.ORACLE18C);

retVal.configuration().set(new DefaultExecuteListenerProvider(listener));

if (isNewLRTS != null) {
String requireBool = isNewLRTS ? "T" : "F";
int requireIntValue = isNewLRTS ? REQUIRE_NEW_LRTS_ID_FORMAT : REQUIRE_OLD_LRTS_ID_FORMAT;
CWMS_UTIL_PACKAGE.call_SET_SESSION_INFO(retVal.configuration(),
SESSION_USE_LRTS_ID_FORMAT, requireBool, requireIntValue);
}
return retVal;
}


protected static Timestamp buildTimestamp(Instant date) {
return date != null ? Timestamp.from(date) : null;
}
Expand All @@ -186,8 +172,8 @@ private static Connection setClientInfo(Context ctx, Connection connection) {
try {
final String apiVersion = ApiServlet.getApiVersion();
connection.setClientInfo("OCSID.ECID",
ApiServlet.APPLICATION_TITLE + " " +
apiVersion.substring(0,Math.min(ORACLE_ECID_MAX_LENGTH,apiVersion.length())));
ApiServlet.APPLICATION_TITLE + " "
+ apiVersion.substring(0, Math.min(ORACLE_ECID_MAX_LENGTH, apiVersion.length())));
if (ctx.handlerType() == HandlerType.BEFORE) {
connection.setClientInfo("OCSID.MODULE", "BEFORE-HANDLER");
} else {
Expand Down Expand Up @@ -219,10 +205,11 @@ protected static Double toDouble(BigDecimal bigDecimal) {

/**
* The idea here is that this will check the current default datum,
* possible switch to the specified datum and
* then run the code and
* if the datum was previously switched
* then switch back to the initial datum.
* possible switch to the specified datum and
* then run the code and
* if the datum was previously switched
* then switch back to the initial datum.
*
* @param targetDatum The desired ver
* @param dslContext
* @param cr
Expand Down Expand Up @@ -251,7 +238,7 @@ protected void withDefaultDatum(@Nullable VerticalDatum targetDatum, DSLContext
* an easy to read manner without having to worry about the syntax.
*/
public static Condition caseInsensitiveLikeRegex(Field<String> field, String regex) {
if("*".equals(regex) || ".*".equals(regex)) {
if ("*".equals(regex) || ".*".equals(regex)) {
return DSL.noCondition();
}
return new CustomCondition() {
Expand Down Expand Up @@ -302,6 +289,7 @@ public static Condition caseInsensitiveLikeRegexNullTrue(Field<String> field, St
* is one of several types of exception (e.q NotFound,
* AlreadyExists, NullArg) that can be specially handled by ApiServlet
* by returning specific HTTP codes or error messages.
*
* @param input the observed exception
* @return An exception, possibly wrapped
*/
Expand Down Expand Up @@ -360,7 +348,7 @@ private static boolean hasCodeOrMessage(SQLException sqlException,
}

private static boolean hasCodeAndMessage(SQLException sqlException,
List<Integer> codes, List<String> segments) {
List<Integer> codes, List<String> segments) {
final String localizedMessage = sqlException.getLocalizedMessage();

return codes.contains(sqlException.getErrorCode())
Expand Down Expand Up @@ -394,26 +382,26 @@ public static boolean isNotFound(RuntimeException input) {

public static boolean isInvalidOffice(RuntimeException input) {
return getSqlException(input)
.map(sqlException -> hasCodeOrMessage(sqlException, Collections.singletonList(20010),
Collections.singletonList("INVALID_OFFICE_ID")))
.orElse(false);
.map(sqlException -> hasCodeOrMessage(sqlException, Collections.singletonList(20010),
Collections.singletonList("INVALID_OFFICE_ID")))
.orElse(false);
}

public static boolean isValueTooLargeException(RuntimeException input) {
return getSqlException(input.getCause()).map(sqlException -> hasCodeOrMessage(sqlException,
Arrays.asList(6502, 12899, 20041),
Arrays.asList("value too large for column", "character string buffer too small",
"Error while writing value at JDBC bind index:")))
.orElse(false);
Arrays.asList(6502, 12899, 20041),
Arrays.asList("value too large for column", "character string buffer too small",
"Error while writing value at JDBC bind index:")))
.orElse(false);
}

public static boolean isTSIDInvalidIntervalException(RuntimeException input) {
return getSqlException(input.getCause()).map(sqlException -> hasCodeAndMessage(sqlException,
Arrays.asList(20205, 20998),
Arrays.asList("Invalid Time Series Description",
"is not a valid interval",
"INVALID Time Series Identifier")))
.orElse(false);
Arrays.asList(20205, 20998),
Arrays.asList("Invalid Time Series Description",
"is not a valid interval",
"INVALID Time Series Identifier")))
.orElse(false);
}

static InvalidItemException buildInvalidTSIDIntervalException(RuntimeException input) {
Expand All @@ -428,12 +416,11 @@ static InvalidItemException buildInvalidTSIDIntervalException(RuntimeException i
if (localizedMessage != null) {
String[] parts = localizedMessage.split("\n");
String errorMessage = parts[0];
if (CURRENT_SCHEMA_VERSION <= SCHEMA_VERSION.V2025_07_01.numeric() && parts.length > 2)
{
if (CURRENT_SCHEMA_VERSION <= SCHEMA_VERSION.V2025_07_01.numeric() && parts.length > 2) {
errorMessage = parts[1];
}
return new InvalidItemException(String.format("Invalid time series description: %s",
errorMessage), cause);
return new InvalidItemException(String.format("Invalid time series description: %s",
errorMessage), cause);
}
return new InvalidItemException("Invalid time series description", cause);
}
Expand Down Expand Up @@ -502,7 +489,7 @@ static CwmsAuthException buildNotAuthorizedForOffice(RuntimeException input) {
}

return new CwmsAuthException("User not authorized for this office.", cause,
HttpServletResponse.SC_UNAUTHORIZED);
HttpServletResponse.SC_UNAUTHORIZED);
}

public static boolean isAlreadyExists(RuntimeException input) {
Expand Down Expand Up @@ -728,8 +715,8 @@ public static boolean isUnsupportedOperationException(RuntimeException input) {
int errorCode = sqlException.getErrorCode();
//procedure doesn't exist
retVal = errorCode == 904
//Table or view does not exist
|| errorCode == 942;
//Table or view does not exist
|| errorCode == 942;
}
return retVal;
}
Expand Down Expand Up @@ -764,8 +751,9 @@ private static UnsupportedOperationException buildUnsupportedOperationException(
/**
* JooqDao provides its own connection() which wraps throw exceptions
* because the DSL.connection() method does not wrap exceptions.
*
* @param dslContext the DSLContext to use
* @param cr the ConnectionRunnable to run with the connection
* @param cr the ConnectionRunnable to run with the connection
*/
protected static void connection(DSLContext dslContext, ConnectionRunnable cr) {
try {
Expand All @@ -779,8 +767,9 @@ protected static void connection(DSLContext dslContext, ConnectionRunnable cr) {
* Like DSL.connection the DSL.connectionResult method does not cause thrown
* exceptions to be wrapped. This method delegates to DSL.connectionResult
* but will wrap exceptions into more specific exception types where possible.
*
* @param dslContext the DSLContext to use
* @param var1 the ConnectionCallable to run with the connection
* @param var1 the ConnectionCallable to run with the connection
*/
protected static <R> R connectionResult(DSLContext dslContext, ConnectionCallable<R> var1) {
try {
Expand Down Expand Up @@ -808,7 +797,7 @@ protected static ZoneId toZoneId(String zoneId, String locationId) {
} else {
if (logger.atFine().isEnabled()) {
logger.atWarning().withCause(e)
.log("Location %s has an invalid location time zone: %s", locationId, zoneId);
.log("Location %s has an invalid location time zone: %s", locationId, zoneId);
} else {
logger.atWarning().log("Location %s has an invalid location time zone: %s", locationId, zoneId);
}
Expand All @@ -820,15 +809,15 @@ protected static ZoneId toZoneId(String zoneId, String locationId) {

public static BigDecimal toBigDecimal(Number number) {
return (number == null) ? null : BigDecimal.valueOf(
number.doubleValue());
number.doubleValue());
}

public static double buildDouble(BigDecimal bigDecimal) {
return (bigDecimal == null) ? 0.0 : bigDecimal.doubleValue();
}

protected static void checkMetaData(ResultSetMetaData metaData, List<String> columnList,
String type) throws SQLException {
String type) throws SQLException {
int columnCount = metaData.getColumnCount();
List<String> metadataColumns = new ArrayList<>();
logger.atFine().log("{0} column dump.", type);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cwms.cda.datasource;

import static cwms.cda.data.dao.Dao.formatBool;
import static org.jooq.SQLDialect.ORACLE18C;

import com.google.common.flogger.FluentLogger;
import cwms.cda.data.dao.JooqDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import org.jooq.DSLContext;
import org.jooq.impl.DSL;
import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE;


/**
* Prepares a connection by setting the LRTS session flag.
*/
public class LrtsSessionPreparer implements ConnectionPreparer {

private static final FluentLogger logger = FluentLogger.forEnclosingClass();

private final Boolean isNewLrts;
private final boolean clearOnClose;

public LrtsSessionPreparer(Boolean isNewLrts) {
this(isNewLrts, false);
}

public LrtsSessionPreparer(Boolean isNewLrts, boolean clear) {
this.isNewLrts = isNewLrts;
clearOnClose = clear;
}

@Override
public Connection prepare(Connection connection) {
if (isNewLrts == null) {
return connection;
}

DSLContext dsl = DSL.using(connection, ORACLE18C);

// Can also get current value and remember it and then reset to that in the close
// if setting with null,null doesn't work.
// GET_SESSION_INFO sessionInfo = CWMS_UTIL_PACKAGE.call_GET_SESSION_INFO(dslContext.configuration()
// , SESSION_USE_LRTS_ID_FORMAT);

String requireBool = formatBool(isNewLrts);
int requireIntValue = isNewLrts ? JooqDao.REQUIRE_NEW_LRTS_ID_FORMAT
: JooqDao.REQUIRE_OLD_LRTS_ID_FORMAT;
CWMS_UTIL_PACKAGE.call_SET_SESSION_INFO(dsl.configuration(),
JooqDao.SESSION_USE_LRTS_ID_FORMAT, requireBool, requireIntValue);
logger.atFine().log("Set LRTS session flag to %s (%d) for connection %s",
requireBool, requireIntValue, connection);

if (clearOnClose) {
// Return a proxy that will unset the flag on close()
return (Connection) Proxy.newProxyInstance(
connection.getClass().getClassLoader(),
new Class<?>[]{Connection.class},
new CloseUnsettingHandler(connection));
} else {
return connection;
}

}

private static class CloseUnsettingHandler implements InvocationHandler {
private final Connection delegate;
private volatile boolean closed = false;

CloseUnsettingHandler(Connection delegate) {
this.delegate = delegate;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
if ("close".equals(name)) {
if (!closed) {
try {
DSLContext dsl = DSL.using(delegate, ORACLE18C);
// Passing null values to clear the session setting
CWMS_UTIL_PACKAGE.call_SET_SESSION_INFO(dsl.configuration(),
JooqDao.SESSION_USE_LRTS_ID_FORMAT, null, null);
logger.atFine().log("Cleared LRTS session flag for connection %s", delegate);
} catch (RuntimeException ex) {
logger.atWarning().withCause(ex)
.log("Failed to clear LRTS session flag on connection close");
} finally {
closed = true;
}
}
return method.invoke(delegate, args);
}
return method.invoke(delegate, args);
}
}
}
Loading
Loading