Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,7 @@ public static SqlQuery addPartKeysToAttrOrTableSqlQuery (SqlQuery attrOrTableSql
if (partKeySqlQuery == null) return attrOrTableSqlQuery;

PrimaryKeyDefinition pkd = recordClass.getPrimaryKeyDefinition();
String idSql = "select " + pkd.createSelectClause(primaryKey.getValues()) +
String idSql = "select " + PrimaryKeyDefinition.createSelectClause(primaryKey.getValues()) +
recordClass.getWdkModel().getAppDb().getPlatform().getDummyTable();

String partSql = partKeySqlQuery.getSql();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ public SingleRecordQuestionParam(RecordClass recordClass) {
@Override
public String toInternalValue(RunnableObj<QueryInstanceSpec> ctxVals) throws WdkModelException {
final String stable = ctxVals.get().get(_param.getName());
PrimaryKeyDefinition pkDef = recordClass.getPrimaryKeyDefinition();
Map<String, String> pk = Functions.mapValues(parseParamValue(stable), entry -> (String)entry.getValue());
return pkDef.createSelectClause(pk);
return PrimaryKeyDefinition.createSelectClause(pk);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import org.gusdb.wdk.model.WdkModelBase;
import org.gusdb.wdk.model.WdkModelException;
import org.gusdb.wdk.model.WdkModelText;
import org.gusdb.wdk.model.WdkRuntimeException;
import org.gusdb.wdk.model.query.Column;
import org.gusdb.wdk.model.query.Query;
import org.gusdb.wdk.model.record.alias.AliasQueryBasedPrimaryKeyAliasPlugin;
import org.gusdb.wdk.model.record.alias.PrimaryKeyAliasPlugin;
import org.gusdb.wdk.model.record.attribute.PkColumnAttributeField;
import org.gusdb.wdk.model.user.User;

Expand Down Expand Up @@ -135,9 +138,9 @@ public void resolveReferences(WdkModel model) throws WdkModelException {

// if alias query specified, resolve/prepare it and create query based plugin
if (_aliasQueryRef != null) {
Query aliasQuery = QueryBasedPrimaryKeyAliasPlugin
Query aliasQuery = AliasQueryBasedPrimaryKeyAliasPlugin
.prepareAliasQuery(_aliasQueryRef, getColumnRefs(), _recordClass);
_aliasPlugin = new QueryBasedPrimaryKeyAliasPlugin(aliasQuery);
_aliasPlugin = new AliasQueryBasedPrimaryKeyAliasPlugin(aliasQuery);
}

// otherwise, resolve the configured alias plugin
Expand All @@ -149,7 +152,7 @@ public void resolveReferences(WdkModel model) throws WdkModelException {
.getDeclaredConstructor()
.newInstance();
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
catch (WdkRuntimeException | InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) {
throw new WdkModelException("Failed instantiating aliasPlugin for class " + _aliasPluginClassName, e);
}
Expand Down Expand Up @@ -199,7 +202,7 @@ public String createJoinClause(String subQuery1Name, String subQuery2Name) {
.collect(Collectors.joining(" AND "));
}

public String createSelectClause(Map<String,String> pkValues) {
public static String createSelectClause(Map<String,String> pkValues) {
List<String> valStrings = new ArrayList<>();
for(String key : pkValues.keySet()) valStrings.add("'" + pkValues.get(key) + "' as " + key);
return String.join(", ", valStrings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import org.gusdb.fgputil.FormatUtil;
Expand Down Expand Up @@ -116,6 +117,14 @@ public static boolean rawValuesDiffer(Map<String, ? extends Object> map1, Map<St
return false;
}

public String createWhereClause(String tableAlias) {
tableAlias = tableAlias == null ? "" : tableAlias.trim();
String tablePrefix = tableAlias.isEmpty() | tableAlias.endsWith(".") ? tableAlias : tableAlias + ".";
return getValues().entrySet().stream()
.map(entry -> tablePrefix + entry.getKey() + " = '" + entry.getValue() + "'")
.collect(Collectors.joining(" AND "));
}

@Override
public boolean equals(Object obj) {
return (obj instanceof PrimaryKeyValue) &&
Expand Down Expand Up @@ -149,4 +158,5 @@ public static List<String[]> toStringArrays(
).toArray(new String[pkValue._pkDef.getColumnRefs().length])
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,7 @@ public static StepFilter resolveStepFilterReferenceByName(String name, WdkModel
* A bulk query is either an original attribute or table query, that is, it either doesn't any param, or
* just one param with the name of Utilities.PARAM_USER_ID.
*/
void validateBulkQuery(Query query) throws WdkModelException {
public void validateBulkQuery(Query query) throws WdkModelException {
validateQuery(query);

// Further limit the attribute/table query to have only user_id param
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.gusdb.wdk.model.record;
package org.gusdb.wdk.model.record.alias;

import java.util.ArrayList;
import java.util.LinkedHashMap;
Expand All @@ -14,6 +14,8 @@
import org.gusdb.wdk.model.query.QueryInstance;
import org.gusdb.wdk.model.query.SqlQuery;
import org.gusdb.wdk.model.query.spec.QueryInstanceSpec;
import org.gusdb.wdk.model.record.RecordClass;
import org.gusdb.wdk.model.record.RecordNotFoundException;
import org.gusdb.wdk.model.user.StepContainer;
import org.gusdb.wdk.model.user.User;

Expand All @@ -23,13 +25,13 @@
*
* @author rdoherty
*/
public class QueryBasedPrimaryKeyAliasPlugin implements PrimaryKeyAliasPlugin {
public class AliasQueryBasedPrimaryKeyAliasPlugin implements PrimaryKeyAliasPlugin {

private static final String ALIAS_OLD_KEY_COLUMN_PREFIX = "old_";

private Query _aliasQuery;

public QueryBasedPrimaryKeyAliasPlugin(Query aliasQuery) {
public AliasQueryBasedPrimaryKeyAliasPlugin(Query aliasQuery) {
_aliasQuery = aliasQuery;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
package org.gusdb.wdk.model.record;
package org.gusdb.wdk.model.record.alias;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.gusdb.wdk.model.WdkModelException;
import org.gusdb.wdk.model.record.RecordNotFoundException;
import org.gusdb.wdk.model.user.User;

/**
* All record class primary key attributes need to be associated either with an alias query
* or an alias query so that the primary key may be determined to be valid. An invalid
* primary key will result in a 404 to the client. A valid primary key that otherwise has
* an issue will result in a 500 to the client. In some cases, there is no way to access
* an issue will result in a 500 to the client. In some cases, there is no way to assess
* the validity of a primary key. In those cases, we need a simple plugin that simply
* returns the primary key given by the client.
*
* @author crisl-adm
*/
public class GenericRecordPrimaryKeyAliasPlugin implements PrimaryKeyAliasPlugin {
public class PassThroughPrimaryKeyAliasPlugin implements PrimaryKeyAliasPlugin {

@Override
public List<Map<String, Object>> getPrimaryKey(User user, Map<String, Object> inputPkValues)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.gusdb.wdk.model.record;
package org.gusdb.wdk.model.record.alias;

import java.util.List;
import java.util.Map;

import org.gusdb.wdk.model.WdkModelException;
import org.gusdb.wdk.model.record.RecordNotFoundException;
import org.gusdb.wdk.model.user.User;

public interface PrimaryKeyAliasPlugin {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package org.gusdb.wdk.model.record.alias;

import static org.gusdb.fgputil.FormatUtil.NL;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.gusdb.fgputil.db.pool.DatabaseInstance;
import org.gusdb.fgputil.db.runner.SQLRunner;
import org.gusdb.fgputil.functional.Functions;
import org.gusdb.wdk.model.Reference;
import org.gusdb.wdk.model.Utilities;
import org.gusdb.wdk.model.WdkModel;
import org.gusdb.wdk.model.WdkModelException;
import org.gusdb.wdk.model.WdkRuntimeException;
import org.gusdb.wdk.model.dbms.ResultList;
import org.gusdb.wdk.model.dbms.SqlResultList;
import org.gusdb.wdk.model.query.Query;
import org.gusdb.wdk.model.query.SqlQuery;
import org.gusdb.wdk.model.query.param.Param;
import org.gusdb.wdk.model.record.PrimaryKeyDefinition;
import org.gusdb.wdk.model.record.PrimaryKeyValue;
import org.gusdb.wdk.model.record.RecordNotFoundException;
import org.gusdb.wdk.model.user.User;

public abstract class QueryBasedPrimaryKeyAliasPlugin implements PrimaryKeyAliasPlugin {

private static final Logger LOG = Logger.getLogger(QueryBasedPrimaryKeyAliasPlugin.class);

private static final Pattern DEFAULT_ID_REGEX = Pattern.compile("^[\\w\\-\\|\\.\\:\\(\\)]+$");

// required
protected abstract String getRecordClassFullName();
protected abstract String getQueryFullName();

// supply PK col value regex but allow subclasses to override
protected boolean pkColValuePassesRegex(String name, String value) {
return DEFAULT_ID_REGEX.matcher(value).find();
}

@Override
public List<Map<String, Object>> getPrimaryKey(User user, Map<String, Object> inputPkValues)
throws WdkModelException, RecordNotFoundException {

WdkModel wdkModel = user.getWdkModel();
DatabaseInstance appDb = wdkModel.getAppDb();

// find the PK def for this lookup
PrimaryKeyDefinition pkDef = wdkModel
.getRecordClassByFullName(getRecordClassFullName())
.orElseThrow(() -> new WdkModelException("Recordclass " + getRecordClassFullName() + " could not be found in this model."))
.getPrimaryKeyDefinition();

PrimaryKeyValue pkValue = Functions.mapException(
() -> new PrimaryKeyValue(pkDef, inputPkValues),
e -> new RecordNotFoundException(e.getMessage()));

LOG.debug("Sequence alias plugin called with pk: " + pkValue.getValuesAsString());

// check for SQL injection
for (Entry<String,String> pkCol : pkValue.getValues().entrySet()) {
if (!pkColValuePassesRegex(pkCol.getKey(), pkCol.getValue())) {
throw new RecordNotFoundException("Value submitted for column " + pkCol.getKey() + " is not valid.");
}
}

Reference queryRef = new Reference(getQueryFullName());
Query query = wdkModel.getQuerySet(queryRef.getSetName()).getQuery(queryRef.getElementName());

if (!(query instanceof SqlQuery)) {
throw new WdkModelException(queryRef.getTwoPartName() + " is not a valid query. " +
getClass().getName() + " must specify a SqlQuery reference.");
}

// use hard-coded select of a single row of PK cols to represent both ID SQL and dataset param internal value
String idSelectSql = "SELECT " + PrimaryKeyDefinition.createSelectClause(pkValue.getValues()) + appDb.getPlatform().getDummyTable();
String querySql = ((SqlQuery)query).getSql().replace(Utilities.MACRO_ID_SQL, idSelectSql);

// disallow partition keys macro; we want all partitions and this encourages
// model writer to choose an unpartitioned table which is more performant
if (querySql.contains(SqlQuery.PARTITION_KEYS_MACRO)) {
throw new WdkModelException("Query '" + queryRef.getTwoPartName() +
" referenced by " + getClass().getName() + " cannot contain the" +
" partition keys macro (" + SqlQuery.PARTITION_KEYS_MACRO + ")");
}

// Get params of specified query
Map<String, Param> paramMap = query.getParamMap();
String sql = switch (paramMap.size()) {

case 0 ->
// if zero params, assume attribute query returning all rows with PKs
"SELECT " + String.join(", ", pkDef.getColumnRefs()) +
" FROM ( " + querySql + " )" +
" WHERE " + pkValue.createWhereClause("");

case 1 ->
// if one param, assume ID search query with single dataset param;
paramMap.values().iterator().next().replaceSql(querySql, null, idSelectSql);

default ->
throw new WdkModelException("Specified query for alias plugin does not contain 0 or 1 parameters.");
};

LOG.debug("Supplied full_id passed regex. Will call the following SQL: " + NL + sql);

List<Map<String, Object>> foundPks = new SQLRunner(appDb.getDataSource(), sql).executeQuery(rs -> {
try {
List<Map<String, Object>> rows = new ArrayList<>();
ResultList result = new SqlResultList(rs);
while (result.next()) {
PrimaryKeyValue row = new PrimaryKeyValue(pkDef, result);
LOG.debug("Found mapped ID: " + row.getValuesAsString());
rows.add(row.getRawValues());
}
return rows;
}
catch(WdkModelException e) {
throw new WdkRuntimeException(e);
}
});

if (foundPks.isEmpty()) {
throw new RecordNotFoundException("Requested primary key '" + pkValue.getValuesAsString() + "' does not map to any sequence.");
}

return foundPks;

}


}
Loading