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
35 changes: 25 additions & 10 deletions webapp/src/main/java/org/apache/atlas/web/rest/DiscoveryREST.java
Original file line number Diff line number Diff line change
Expand Up @@ -891,28 +891,43 @@ private void validateSearchParameters(SearchParameters parameters) throws AtlasB
}

private void validateEntityFilter(SearchParameters parameters) throws AtlasBaseException {
FilterCriteria entityFilter = parameters.getEntityFilters();
validateNestedCriteria(parameters.getEntityFilters());
validateNestedCriteria(parameters.getTagFilters());
}

private void validateNestedCriteria(SearchParameters.FilterCriteria criteria) throws AtlasBaseException {
if (criteria == null) {
return; // Nothing to validate
}

if (entityFilter == null) {
boolean hasComposite = criteria.getCriterion() != null && !criteria.getCriterion().isEmpty();
boolean hasLeaf = StringUtils.isNotEmpty(criteria.getAttributeName())
|| criteria.getOperator() != null
|| StringUtils.isNotEmpty(criteria.getAttributeValue());

if (!hasComposite && !hasLeaf) {
// It's an empty filter object — skip (backward compatibility)
return;
}

if (entityFilter.getCriterion() != null &&
!entityFilter.getCriterion().isEmpty()) {
if (entityFilter.getCondition() == null || StringUtils.isEmpty(entityFilter.getCondition().toString())) {
if (hasComposite) {
if (criteria.getCondition() == null || StringUtils.isEmpty(criteria.getCondition().toString())) {
throw new AtlasBaseException("Condition (AND/OR) must be specified when using multiple filters.");
}

for (FilterCriteria filterCriteria : entityFilter.getCriterion()) {
validateCriteria(filterCriteria);
for (FilterCriteria filterCriteria : criteria.getCriterion()) {
if (filterCriteria != null) {
validateNestedCriteria(filterCriteria); // Recursive check
}
}
}
else {
validateCriteria(entityFilter);

if (hasLeaf) {
validateLeafFilterCriteria(criteria);
}
}

private void validateCriteria(SearchParameters.FilterCriteria criteria) throws AtlasBaseException {
private void validateLeafFilterCriteria(SearchParameters.FilterCriteria criteria) throws AtlasBaseException {
if (criteria.getOperator() == null) {
throw new AtlasBaseException(AtlasErrorCode.INVALID_OPERATOR, criteria.getAttributeName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.apache.atlas.model.typedef.AtlasTypesDef;
import org.apache.atlas.type.AtlasTypeUtil;
import org.apache.atlas.utils.TestResourceFileUtils;
import org.apache.commons.lang.StringUtils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
Expand All @@ -46,7 +47,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.function.Predicate;

import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.PUBLIC_ONLY;
Expand Down Expand Up @@ -228,21 +228,33 @@ public void testSavedSearch(String jsonFile) {
}

@Test
public void testAttributeSearchInvalidOperator() {
runNegativeSearchTest("search-parameters/operator", "ATLAS-400-00-103", parameters -> parameters.getEntityFilters() != null && parameters.getEntityFilters().getOperator() != null);
public void testComplexFilterInvalidOperator() {
runNegativeSearchTestForBasicSearch("search-parameters/operator", "ATLAS-400-00-103",
parameters -> (parameters.getEntityFilters() != null && containsAnyOperator(parameters.getEntityFilters())) ||
(parameters.getTagFilters() != null && containsAnyOperator(parameters.getTagFilters())));
runNegativeSearchTestForQuickSearch("search-parameters/operator", "ATLAS-400-00-103",
parameters -> parameters.getEntityFilters() != null && containsAnyOperator(parameters.getEntityFilters()));
}

@Test
public void testAttributeSearchEmptyNameAttribute() {
runNegativeSearchTest("search-parameters/attribute-name", "ATLAS-400-00-104", parameters -> parameters.getEntityFilters() != null && parameters.getEntityFilters().getAttributeName() != null);
public void testComplexFilterEmptyAttributeName() {
runNegativeSearchTestForBasicSearch("search-parameters/attribute-name", "ATLAS-400-00-104",
parameters -> (parameters.getEntityFilters() != null && containsAnyAttributeName(parameters.getEntityFilters())) ||
(parameters.getTagFilters() != null && containsAnyAttributeName(parameters.getTagFilters())));
runNegativeSearchTestForQuickSearch("search-parameters/attribute-name", "ATLAS-400-00-104",
parameters -> parameters.getEntityFilters() != null && containsAnyAttributeName(parameters.getEntityFilters()));
}

@Test
public void testAttributeSearchEmptyValueAttribute() {
runNegativeSearchTest("search-parameters/attribute-value", "ATLAS-400-00-105", parameters -> parameters.getEntityFilters() != null && parameters.getEntityFilters().getAttributeValue() != null);
public void testComplexFilterEmptyAttributeValue() {
runNegativeSearchTestForBasicSearch("search-parameters/attribute-value", "ATLAS-400-00-105",
parameters -> (parameters.getEntityFilters() != null && containsAnyAttributeValue(parameters.getEntityFilters())) ||
(parameters.getTagFilters() != null && containsAnyAttributeValue(parameters.getTagFilters())));
runNegativeSearchTestForQuickSearch("search-parameters/attribute-value", "ATLAS-400-00-105",
parameters -> parameters.getEntityFilters() != null && containsAnyAttributeValue(parameters.getEntityFilters()));
}

public void runNegativeSearchTest(String jsonFile, String expectedErrorCode, java.util.function.Predicate<SearchParameters> paramFilter) {
public void runNegativeSearchTestForBasicSearch(String jsonFile, String expectedErrorCode, java.util.function.Predicate<SearchParameters> paramFilter) {
try {
BasicSearchParametersWithExpectation[] testExpectations = TestResourceFileUtils.readObjectFromJson(jsonFile, BasicSearchParametersWithExpectation[].class);
assertNotNull(testExpectations);
Expand All @@ -264,6 +276,66 @@ public void runNegativeSearchTest(String jsonFile, String expectedErrorCode, jav
}
}

public void runNegativeSearchTestForQuickSearch(String jsonFile, String expectedErrorCode, java.util.function.Predicate<QuickSearchParameters> qspParamFilter) {
try {
BasicSearchParametersWithExpectation[] testExpectations = TestResourceFileUtils.readObjectFromJson(jsonFile, BasicSearchParametersWithExpectation[].class);
assertNotNull(testExpectations);
Arrays
.stream(testExpectations)
.map(testExpectation -> testExpectation.getSearchParameters())
.map(sp -> {
QuickSearchParameters qsp = new QuickSearchParameters();
qsp.setTypeName(sp.getTypeName());
qsp.setEntityFilters(sp.getEntityFilters());
return qsp; })
.filter(qspParamFilter)
.forEach(params -> {
try {
atlasClientV2.quickSearch(params);
}
catch (AtlasServiceException e) {
assertTrue(e.getMessage().contains(expectedErrorCode),
"Expected error code " + expectedErrorCode + " in exception message: " + e.getMessage());
}
});
} catch (IOException e) {
fail(e.getMessage());
}
}

private boolean containsAnyMatchingCriteria(SearchParameters.FilterCriteria filterCriteria, java.util.function.Predicate<SearchParameters.FilterCriteria> matcher) {
if (filterCriteria == null) {
return false;
}
if (matcher.test(filterCriteria)) {
return true;
}
if (filterCriteria.getCriterion() != null) {
return filterCriteria.getCriterion().stream()
.filter(fc -> fc != null)
.anyMatch(fc -> containsAnyMatchingCriteria(fc, matcher));
}

return false;
}

private boolean containsAnyOperator(SearchParameters.FilterCriteria filter) {
return containsAnyMatchingCriteria(filter, fc -> {
if (fc.getOperator() == null) {
return true;
}
return SearchParameters.Operator.fromString(fc.getOperator().toString()) == null;
});
}

private boolean containsAnyAttributeName(SearchParameters.FilterCriteria filter) {
return containsAnyMatchingCriteria(filter, fc -> StringUtils.isBlank(fc.getAttributeName()));
}

private boolean containsAnyAttributeValue(SearchParameters.FilterCriteria filter) {
return containsAnyMatchingCriteria(filter, fc -> StringUtils.isBlank(fc.getAttributeValue()));
}

@Test(dependsOnMethods = "testSavedSearch")
public void testExecuteSavedSearchByName() {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"offset": 0,
"entityFilters": {
"attributeName": "name",
"operator": "contains",
"attributeValue": "testtable",
"condition" : "OR",
"criterion" : [
Expand Down
189 changes: 156 additions & 33 deletions webapp/src/test/resources/json/search-parameters/attribute-name.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,157 @@
[ {
"testDescription": "hive_table contains testtable or retentionSize != 0",
"searchParameters": {
"typeName": "hive_table",
"excludeDeletedEntities": true,
"classification": "",
"query": "",
"limit": 25,
"offset": 0,
"entityFilters": {
"attributeName": "",
"attributeValue": "",
"condition" : "AND",
"criterion" : [
{
"attributeName": "",
"operator": "eq",
"attributeValue": "testtable"
},
{
"attributeName": "retention",
"operator": "neq",
"attributeValue": "0"
}
]
},
"tagFilters": null,
"attributes": [
""
]
[
{
"testDescription": "Invalid attribute name is empty",
"searchParameters": {
"typeName": "hive_table",
"excludeDeletedEntities": true,
"limit": 25,
"offset": 0,
"entityFilters": {
"condition": "AND",
"criterion": [
{
"attributeName": "",
"operator": "eq",
"attributeValue": "testtable"
},
{
"attributeName": "",
"operator": "neq",
"attributeValue": "0"
}
]
},
"tagFilters": null,
"attributes": [""]
}
},
"expectedCount": 0
}
]
{
"testDescription": "Nested invalid attribute names",
"searchParameters": {
"typeName": "hive_table",
"excludeDeletedEntities": true,
"limit": 25,
"offset": 0,
"entityFilters": {
"condition": "AND",
"criterion": [
{
"condition": "AND",
"criterion": [
{
"attributeName": "",
"operator": "eq",
"attributeValue": "hive"
},
{
"attributeName": "",
"operator": "neq",
"attributeValue": "hive"
}
]
},
{
"attributeName": "",
"operator": "neq",
"attributeValue": ""
}
]
},
"tagFilters": null,
"attributes": [""]
}
},
{
"testDescription": "Only nested tagFilters with invalid (empty) attribute names",
"searchParameters": {
"typeName": "hive_column",
"excludeDeletedEntities": true,
"limit": 25,
"offset": 0,
"entityFilters": null,
"tagFilters": {
"condition": "AND",
"criterion": [
{
"condition": "OR",
"criterion": [
{
"attributeName": "",
"operator": "eq",
"attributeValue": "admin"
},
{
"attributeName": "",
"operator": "neq",
"attributeValue": "test_value"
}
]
},
{
"attributeName": "",
"operator": "eq",
"attributeValue": "someValue"
}
]
},
"attributes": [""],
"classification": "_ALL_CLASSIFICATION_TYPES"
}
},
{
"testDescription": "Nested entityFilters and tagFilters with invalid (empty) attribute names",
"searchParameters": {
"typeName": "hive_table",
"excludeDeletedEntities": true,
"limit": 25,
"offset": 0,
"entityFilters": {
"condition": "AND",
"criterion": [
{
"condition": "AND",
"criterion": [
{
"attributeName": "",
"operator": "eq",
"attributeValue": "testtable"
},
{
"attributeName": "",
"operator": "eq",
"attributeValue": "testuser"
}
]
},
{
"attributeName": "",
"operator": "neq",
"attributeValue": "0"
}
]
},
"tagFilters": {
"condition": "AND",
"criterion": [
{
"condition": "OR",
"criterion": [
{
"attributeName": "",
"operator": "eq",
"attributeValue": "v1"
},
{
"attributeName": "",
"operator": "eq",
"attributeValue": "someValue"
}
]
}
]
},
"attributes": [""],
"classification": "_ALL_CLASSIFICATION_TYPES"
}
}
]
Loading