Skip to content

Commit

Permalink
Each BackendQuery type knows which search types it supports. Changes …
Browse files Browse the repository at this point in the history
…in validation.
  • Loading branch information
luk-kaminski committed Dec 13, 2024
1 parent 49571bc commit 7ac06e6
Show file tree
Hide file tree
Showing 12 changed files with 215 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.graylog.plugins.views.search.engine.BackendQuery;
import org.graylog.plugins.views.search.rest.SearchTypeExecutionState;
import org.graylog.plugins.views.search.searchfilters.model.UsedSearchFilter;
import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType;
import org.graylog.plugins.views.search.timeranges.DerivedTimeRange;
import org.graylog2.contentpacks.ContentPackable;
import org.graylog2.contentpacks.EntityDescriptorIds;
Expand Down Expand Up @@ -140,7 +141,7 @@ interface Result {
}

@JsonAutoDetect
class Fallback implements SearchType {
class Fallback implements SearchEngineSearchType {

@JsonProperty
private String type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import jakarta.validation.constraints.NotNull;
import org.graylog.plugins.views.search.SearchType;
import org.graylog.plugins.views.search.engine.BackendQuery;
import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType;

import javax.annotation.Nullable;

import jakarta.validation.constraints.NotNull;

import java.util.Optional;
import java.util.Set;

@AutoValue
@JsonAutoDetect
Expand Down Expand Up @@ -65,6 +66,11 @@ public static ElasticsearchQueryString create(final @JsonProperty("type") String
@Override
public abstract String queryString();

@JsonIgnore
public Set<Class<? extends SearchType>> supportedSearchTypes() {
return Set.of(SearchEngineSearchType.class);
}

@JsonIgnore
public boolean isEmpty() {
String trimmed = queryString().trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
*/
package org.graylog.plugins.views.search.engine;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.graylog.plugins.views.search.SearchType;
import org.graylog.plugins.views.search.elasticsearch.ElasticsearchQueryString;

import java.util.Set;

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = BackendQuery.TYPE_FIELD,
Expand All @@ -30,4 +34,7 @@ public interface BackendQuery {
String type();

String queryString();

@JsonIgnore
Set<Class<? extends SearchType>> supportedSearchTypes();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.graylog.plugins.views.search.engine.normalization.SearchNormalization;
import org.graylog.plugins.views.search.engine.validation.DataWarehouseSearchValidator;
import org.graylog.plugins.views.search.engine.validation.PluggableSearchValidation;
import org.graylog.plugins.views.search.engine.validation.SearchTypesMatchBackendQueryValidator;
import org.graylog.plugins.views.search.engine.validation.SearchValidation;
import org.graylog.plugins.views.search.engine.validation.TimeRangeValidator;

Expand All @@ -35,6 +36,7 @@ protected void configure() {
// Triggering set binder explicitly, so no injection errors are being caused if no implementation is bound.
searchPostValidationNormalizerBinder();
registerSearchValidator(TimeRangeValidator.class);
registerSearchValidator(SearchTypesMatchBackendQueryValidator.class);
//while DW searches are enterprise only, the validation should probably be present in gl-server, i.e. to discover invalid searches even if a client has lost the license
registerSearchValidator(DataWarehouseSearchValidator.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
package org.graylog.plugins.views.search.engine.validation;

import com.google.common.collect.ImmutableSet;
import org.graylog.plugins.views.search.DataWarehouseSearchType;
import org.graylog.plugins.views.search.Query;
import org.graylog.plugins.views.search.Search;
import org.graylog.plugins.views.search.SearchType;
import org.graylog.plugins.views.search.errors.QueryError;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.errors.SearchTypeError;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.searchtypes.DataWarehouseSearchType;

import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -58,14 +58,13 @@ public Set<SearchError> validate(final Query query,
return Set.of(new QueryError(query, "Data Warehouse query can contain only one search type"));
}
final Optional<SearchType> first = searchTypes.stream().findFirst();
if (!(first.get() instanceof DataWarehouseSearchType)) {
return Set.of(new SearchTypeError(query, first.get().id(), "Data Warehouse query can contain only data warehouse search types"));
} else {
final Set<String> streams = first.get().streams();
if (streams == null || streams.size() > 1) {
return Set.of(new SearchTypeError(query, first.get().id(), "Data Warehouse preview can be executed on only 1 stream, search type contained more"));
}

final Set<String> streams = first.get().streams();
if (streams == null || streams.size() > 1) {
return Set.of(new SearchTypeError(query, first.get().id(),
"Data Warehouse preview can be executed on only 1 stream, search type contained more"));
}

}
return Set.of();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
package org.graylog.plugins.views.search.engine.validation;

import org.graylog.plugins.views.search.Query;
import org.graylog.plugins.views.search.Search;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.errors.SearchTypeError;
import org.graylog.plugins.views.search.permissions.SearchUser;

import java.util.Set;
import java.util.stream.Collectors;

public class SearchTypesMatchBackendQueryValidator implements SearchValidator {

public static final String INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG = "Invalid search type for given query type";

@Override
public Set<SearchError> validate(final Search search,
final SearchUser searchUser) {
return search.queries()
.stream()
.flatMap(query -> validate(query, searchUser).stream())
.collect(Collectors.toSet());
}

@Override
public Set<SearchError> validate(final Query query,
final SearchUser searchUser) {
return query.searchTypes().stream()
.filter(searchType -> query.query()
.supportedSearchTypes()
.stream()
.anyMatch(supportedType -> !supportedType.isAssignableFrom(searchType.getClass())))
.map(searchType -> new SearchTypeError(query, searchType.id(), INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG))
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
package org.graylog.plugins.views.search;
package org.graylog.plugins.views.search.searchtypes;

import org.graylog.plugins.views.search.SearchType;

/**
* Marker interface for search types that are not search engine related, but Data Warehouse/Iceberg related
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
@AutoValue
@JsonTypeName(MessageList.NAME)
@JsonDeserialize(builder = MessageList.Builder.class)
public abstract class MessageList implements SearchType {
public abstract class MessageList implements SearchEngineSearchType {
public static final String NAME = "messages";

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
package org.graylog.plugins.views.search.searchtypes;

import org.graylog.plugins.views.search.SearchType;

/**
* Marker interface for search types that are search engine related.
*/
public interface SearchEngineSearchType extends SearchType {
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.graylog.plugins.views.search.engine.BackendQuery;
import org.graylog.plugins.views.search.rest.SearchTypeExecutionState;
import org.graylog.plugins.views.search.searchfilters.model.UsedSearchFilter;
import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType;
import org.graylog.plugins.views.search.timeranges.DerivedTimeRange;
import org.graylog2.contentpacks.EntityDescriptorIds;
import org.graylog2.contentpacks.model.entities.EntityDescriptor;
Expand All @@ -52,7 +53,7 @@
@AutoValue
@JsonTypeName(EventList.NAME)
@JsonDeserialize(builder = EventList.Builder.class)
public abstract class EventList implements SearchType, HasAttributeFilter {
public abstract class EventList implements SearchEngineSearchType, HasAttributeFilter {
public static final int DEFAULT_PAGE_SIZE = 10;
public static final String NAME = "events";
public static final Set<String> KNOWN_ATTRIBUTES = Set.of("priority", "event_definition_id", "alert");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.graylog.plugins.views.search.engine.BackendQuery;
import org.graylog.plugins.views.search.rest.SearchTypeExecutionState;
import org.graylog.plugins.views.search.searchfilters.model.UsedSearchFilter;
import org.graylog.plugins.views.search.searchtypes.SearchEngineSearchType;
import org.graylog.plugins.views.search.timeranges.DerivedTimeRange;
import org.graylog2.contentpacks.EntityDescriptorIds;
import org.graylog2.contentpacks.model.entities.EntityDescriptor;
Expand All @@ -47,7 +48,7 @@
@AutoValue
@JsonTypeName(Pivot.NAME)
@JsonDeserialize(builder = Pivot.Builder.class)
public abstract class Pivot implements SearchType {
public abstract class Pivot implements SearchEngineSearchType {
public static final String NAME = "pivot";

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
package org.graylog.plugins.views.search.engine.validation;

import org.graylog.plugins.views.search.Query;
import org.graylog.plugins.views.search.SearchType;
import org.graylog.plugins.views.search.elasticsearch.ElasticsearchQueryString;
import org.graylog.plugins.views.search.errors.SearchError;
import org.graylog.plugins.views.search.permissions.SearchUser;
import org.graylog.plugins.views.search.searchtypes.MessageList;
import org.graylog.plugins.views.search.searchtypes.pivot.Pivot;
import org.graylog.plugins.views.search.searchtypes.pivot.buckets.Values;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.graylog.plugins.views.search.engine.validation.SearchTypesMatchBackendQueryValidator.INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

@ExtendWith(MockitoExtension.class)
class SearchTypesMatchBackendQueryValidatorTest {

private SearchTypesMatchBackendQueryValidator toTest;
@Mock
private SearchUser searchUser;

@BeforeEach
void setUp() {
toTest = new SearchTypesMatchBackendQueryValidator();
}

@Test
void testValidationIsOkOnProperSearchTypes() {
Query query = Query.builder()
.query(ElasticsearchQueryString.of("bayobongo"))
.searchTypes(Set.of(
MessageList.builder()
.id("messageListId")
.build(),

Pivot.builder()
.rowGroups(
Values.builder()
.fields(List.of("id", "whatever"))
.limit(10)
.build()
)
.series(List.of())
.rollup(false)
.build()
))
.build();

assertEquals(Set.of(), toTest.validate(query, searchUser));
}

@Test
void testValidationDiscoversWrongSearchTypes() {
final SearchType wrongSearchType = mock(SearchType.class);
doReturn("wrong!").when(wrongSearchType).id();
Query query = Query.builder()
.id("query_id")
.query(ElasticsearchQueryString.of("bayobongo"))
.searchTypes(Set.of(
MessageList.builder()
.id("messageListId")
.build(),

wrongSearchType
))
.build();

final Set<SearchError> errors = toTest.validate(query, searchUser);
assertThat(errors)
.isNotNull()
.hasSize(1);
assertThat(errors.stream().findFirst().get().description())
.isEqualTo(INVALID_SEARCH_TYPE_FOR_GIVEN_QUERY_TYPE_MSG);

}
}

0 comments on commit 7ac06e6

Please sign in to comment.