Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support shard request cache for queries with DLS and FLS #70191

Merged
merged 56 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d350e86
[POC] Enabled Shard Request Cache with DLS/FLS
tvernum Feb 23, 2021
41f7b36
tweak
ywangd Mar 9, 2021
1526579
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Mar 10, 2021
76de812
Split shardsearch and search interceptors, more tweak
ywangd Mar 10, 2021
fca4079
address feedback before exploring the plugin extension approach
ywangd Mar 17, 2021
5cc3b4c
Merge branch 'master' into dls-req-cache
elasticmachine Mar 17, 2021
741bc12
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Mar 21, 2021
fdb6b0b
Merge branch 'dls-req-cache' of github.com:ywangd/elasticsearch into …
ywangd Mar 21, 2021
8cceb81
Use search plugin extension point instead of interceptor
ywangd Mar 21, 2021
5e72414
fix tests
ywangd Mar 21, 2021
592e775
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Mar 21, 2021
4c65bc4
remove cacheModifier field
ywangd Mar 21, 2021
31adadf
reinstate interceptor code for bwc
ywangd Mar 22, 2021
50ff3e8
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Mar 22, 2021
b596f32
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 3, 2021
0704a56
Address feedback
ywangd Jun 3, 2021
47f98d1
checkstyle
ywangd Jun 3, 2021
907c6aa
Working on tests
ywangd Jun 3, 2021
5afcd7b
Add tests also disable request cache for CCS with DLS/FLS.
ywangd Jun 3, 2021
dd682fc
checkstyle
ywangd Jun 7, 2021
e99b9ea
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 7, 2021
3be72b4
checkstyle again
ywangd Jun 7, 2021
e4fbc29
Fix tests
ywangd Jun 7, 2021
4fa52a9
tweak
ywangd Jun 7, 2021
a91af61
fix test: sorted field security
ywangd Jun 7, 2021
2485098
mute rest compat temporarily for get user privileges
ywangd Jun 7, 2021
bcf91ea
err test name
ywangd Jun 7, 2021
7ff890d
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 7, 2021
92329ec
Fix tests
ywangd Jun 7, 2021
b9d8815
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 16, 2021
118b9ce
fix for merging master
ywangd Jun 16, 2021
e694f87
Apply suggestions from code review
ywangd Jun 17, 2021
cba7e03
fix compile error due to merging
ywangd Jun 17, 2021
b576759
address feedback for searchplugin extension point
ywangd Jun 17, 2021
b3b4f69
Revert "address feedback for searchplugin extension point"
ywangd Jun 22, 2021
e2cbee4
Address feedback to use BiConsumer instead of named class.
ywangd Jun 22, 2021
e00da2f
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 22, 2021
15e284a
tweak
ywangd Jun 22, 2021
91a079d
checkstyle
ywangd Jun 22, 2021
3550c9f
more checkstyle
ywangd Jun 22, 2021
2e51560
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 22, 2021
a4302ec
Update server/src/main/java/org/elasticsearch/search/SearchModule.java
ywangd Jun 23, 2021
e92a6de
Address feedback
ywangd Jun 23, 2021
eec5b03
fix tests
ywangd Jun 23, 2021
5996218
Add test for DLS with template role query
ywangd Jun 25, 2021
efad6a1
Fix for DLS template role query
ywangd Jun 25, 2021
e02d7ef
Merge remote-tracking branch 'origin/master' into dls-req-cache
ywangd Jun 25, 2021
cbac181
Add tests for request cache with DLS role query using painless
ywangd Jun 26, 2021
9a80d49
Fix for template role query with painless
ywangd Jun 27, 2021
27f8056
checkstyle
ywangd Jun 27, 2021
933658a
fix tests
ywangd Jun 27, 2021
9724956
fix more tests
ywangd Jun 27, 2021
7a42567
disable request cache if DLS role query uses painless
ywangd Jun 28, 2021
7a99042
tweak
ywangd Jun 28, 2021
b12fe57
tweak comments
ywangd Jun 28, 2021
1aa1193
address feedback
ywangd Jun 28, 2021
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 @@ -33,6 +33,7 @@
import org.elasticsearch.cluster.routing.RecoverySource;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.core.CheckedConsumer;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.common.CheckedSupplier;
Expand Down Expand Up @@ -227,6 +228,7 @@ public class IndicesService extends AbstractLifecycleComponent
private final boolean nodeWriteDanglingIndicesInfo;
private final ValuesSourceRegistry valuesSourceRegistry;
private final TimestampFieldMapperService timestampFieldMapperService;
private final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> requestCacheKeyDifferentiator;

@Override
protected void doStart() {
Expand All @@ -246,7 +248,8 @@ public IndicesService(Settings settings, PluginsService pluginsService, NodeEnvi
Map<String, IndexStorePlugin.DirectoryFactory> directoryFactories, ValuesSourceRegistry valuesSourceRegistry,
Map<String, IndexStorePlugin.RecoveryStateFactory> recoveryStateFactories,
List<IndexStorePlugin.IndexFoldersDeletionListener> indexFoldersDeletionListeners,
Map<String, IndexStorePlugin.SnapshotCommitSupplier> snapshotCommitSuppliers) {
Map<String, IndexStorePlugin.SnapshotCommitSupplier> snapshotCommitSuppliers,
CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> requestCacheKeyDifferentiator) {
this.settings = settings;
this.threadPool = threadPool;
this.pluginsService = pluginsService;
Expand Down Expand Up @@ -295,6 +298,7 @@ public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, lon
this.recoveryStateFactories = recoveryStateFactories;
this.indexFoldersDeletionListeners = new CompositeIndexFoldersDeletionListener(indexFoldersDeletionListeners);
this.snapshotCommitSuppliers = snapshotCommitSuppliers;
this.requestCacheKeyDifferentiator = requestCacheKeyDifferentiator;
// doClose() is called when shutting down a node, yet there might still be ongoing requests
// that we need to wait for before closing some resources such as the caches. In order to
// avoid closing these resources while ongoing requests are still being processed, we use a
Expand Down Expand Up @@ -1399,7 +1403,7 @@ public void loadIntoContext(ShardSearchRequest request, SearchContext context, Q
final DirectoryReader directoryReader = context.searcher().getDirectoryReader();

boolean[] loadedFromCache = new boolean[] { true };
BytesReference cacheKey = request.cacheKey();
BytesReference cacheKey = request.cacheKey(requestCacheKeyDifferentiator);
BytesReference bytesReference = cacheShardLevelResult(
context.indexShard(),
context.getSearchExecutionContext().mappingCacheKey(),
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/java/org/elasticsearch/node/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ protected Node(final Environment initialEnvironment,
threadPool, settingsModule.getIndexScopedSettings(), circuitBreakerService, bigArrays, scriptService,
clusterService, client, metaStateService, engineFactoryProviders, indexStoreFactories,
searchModule.getValuesSourceRegistry(), recoveryStateFactories, indexFoldersDeletionListeners,
snapshotCommitSuppliers);
snapshotCommitSuppliers, searchModule.getRequestCacheKeyDifferentiator());

final AliasValidator aliasValidator = new AliasValidator();

Expand Down
14 changes: 14 additions & 0 deletions server/src/main/java/org/elasticsearch/plugins/SearchPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@
package org.elasticsearch.plugins;

import org.apache.lucene.search.Query;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.common.xcontent.ParseField;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.lucene.search.function.ScoreFunction;
import org.elasticsearch.common.xcontent.ContextParser;
import org.elasticsearch.common.xcontent.XContent;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryParser;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
Expand All @@ -34,6 +37,7 @@
import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry;
import org.elasticsearch.search.fetch.FetchSubPhase;
import org.elasticsearch.search.fetch.subphase.highlight.Highlighter;
import org.elasticsearch.search.internal.ShardSearchRequest;
import org.elasticsearch.search.rescore.Rescorer;
import org.elasticsearch.search.rescore.RescorerBuilder;
import org.elasticsearch.search.suggest.Suggest;
Expand Down Expand Up @@ -124,6 +128,16 @@ default List<RescorerSpec<?>> getRescorers() {
return emptyList();
}

/**
* Allows plugins to register a cache differentiator which contributes to the cacheKey
* computation for the request cache. This helps differentiate between queries that
* are otherwise identical.
*/
@Nullable
default CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> getRequestCacheKeyDifferentiator() {
return null;
}

/**
* Specification of custom {@link ScoreFunction}.
*/
Expand Down
28 changes: 28 additions & 0 deletions server/src/main/java/org/elasticsearch/search/SearchModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@
package org.elasticsearch.search;

import org.apache.lucene.search.BooleanQuery;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.common.NamedRegistry;
import org.elasticsearch.common.xcontent.ParseField;
import org.elasticsearch.common.geo.GeoShapeType;
import org.elasticsearch.common.geo.ShapesAvailability;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.BoostingQueryBuilder;
import org.elasticsearch.index.query.CombinedFieldsQueryBuilder;
Expand Down Expand Up @@ -223,6 +226,7 @@
import org.elasticsearch.search.fetch.subphase.highlight.Highlighter;
import org.elasticsearch.search.fetch.subphase.highlight.PlainHighlighter;
import org.elasticsearch.search.fetch.subphase.highlight.UnifiedHighlighter;
import org.elasticsearch.search.internal.ShardSearchRequest;
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
import org.elasticsearch.search.rescore.RescorerBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
Expand All @@ -244,6 +248,7 @@
import org.elasticsearch.search.suggest.term.TermSuggestion;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -271,6 +276,7 @@ public class SearchModule {
private final List<NamedWriteableRegistry.Entry> namedWriteables = new ArrayList<>();
private final List<NamedXContentRegistry.Entry> namedXContents = new ArrayList<>();
private final ValuesSourceRegistry valuesSourceRegistry;
private final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> requestCacheKeyDifferentiator;

/**
* Constructs a new SearchModule object
Expand All @@ -296,6 +302,7 @@ public SearchModule(Settings settings, List<SearchPlugin> plugins) {
registerSearchExts(plugins);
registerShapes();
registerIntervalsSourceProviders();
requestCacheKeyDifferentiator = registerRequestCacheKeyDifferentiator(plugins);
namedWriteables.addAll(SortValue.namedWriteables());
}

Expand All @@ -311,6 +318,11 @@ public ValuesSourceRegistry getValuesSourceRegistry() {
return valuesSourceRegistry;
}

@Nullable
public CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> getRequestCacheKeyDifferentiator() {
ywangd marked this conversation as resolved.
Show resolved Hide resolved
return requestCacheKeyDifferentiator;
}

/**
* Returns the {@link Highlighter} registry
*/
Expand Down Expand Up @@ -833,6 +845,22 @@ private void registerIntervalsSourceProviders() {
namedWriteables.addAll(getIntervalsSourceProviderNamedWritables());
}

private CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> registerRequestCacheKeyDifferentiator(
List<SearchPlugin> plugins) {
CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> differentiator = null;
for (SearchPlugin plugin : plugins) {
final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> d = plugin.getRequestCacheKeyDifferentiator();
if (d != null) {
if (differentiator == null) {
differentiator = d;
} else {
throw new IllegalArgumentException("Cannot have more than one plugin providing a request cache key differentiator");
}
}
}
return differentiator;
}

public static List<NamedWriteableRegistry.Entry> getIntervalsSourceProviderNamedWritables() {
return List.of(
new NamedWriteableRegistry.Entry(IntervalsSourceProvider.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.core.CheckedFunction;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.common.Strings;
Expand Down Expand Up @@ -396,10 +397,13 @@ public TimeValue keepAlive() {
/**
* Returns the cache key for this shard search request, based on its content
*/
public BytesReference cacheKey() throws IOException {
public BytesReference cacheKey(CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> differentiator) throws IOException {
BytesStreamOutput out = scratch.get();
try {
this.innerWriteTo(out, true);
if (differentiator != null) {
differentiator.accept(this, out);
}
// copy it over since we don't want to share the thread-local bytes in #scratch
return out.copyBytes();
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.util.CharsRefBuilder;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
Expand Down Expand Up @@ -48,6 +49,7 @@
import org.elasticsearch.search.fetch.subphase.highlight.Highlighter;
import org.elasticsearch.search.fetch.subphase.highlight.PlainHighlighter;
import org.elasticsearch.search.fetch.subphase.highlight.UnifiedHighlighter;
import org.elasticsearch.search.internal.ShardSearchRequest;
import org.elasticsearch.search.rescore.QueryRescorerBuilder;
import org.elasticsearch.search.rescore.RescoreContext;
import org.elasticsearch.search.rescore.RescorerBuilder;
Expand Down Expand Up @@ -76,7 +78,10 @@
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.nullValue;

public class SearchModuleTests extends ESTestCase {

Expand Down Expand Up @@ -296,6 +301,40 @@ public List<RescorerSpec<?>> getRescorers() {
hasSize(1));
}

public void testRegisterNullRequestCacheKeyDifferentiator() {
final SearchModule module = new SearchModule(Settings.EMPTY, List.of());
assertThat(module.getRequestCacheKeyDifferentiator(), nullValue());
}

public void testRegisterRequestCacheKeyDifferentiator() {
final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> requestCacheKeyDifferentiator = (r, o) -> { };
final SearchModule module = new SearchModule(Settings.EMPTY, List.of(new SearchPlugin() {
@Override
public CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> getRequestCacheKeyDifferentiator() {
return requestCacheKeyDifferentiator;
}
}));
assertThat(module.getRequestCacheKeyDifferentiator(), equalTo(requestCacheKeyDifferentiator));
}

public void testCannotRegisterMultipleRequestCacheKeyDifferentiators() {
final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> differentiator1 = (r, o) -> {};
final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> differentiator2 = (r, o) -> {};
final IllegalArgumentException e =
expectThrows(IllegalArgumentException.class, () -> new SearchModule(Settings.EMPTY, List.of(new SearchPlugin() {
@Override
public CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> getRequestCacheKeyDifferentiator() {
return differentiator1;
}
}, new SearchPlugin() {
@Override
public CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> getRequestCacheKeyDifferentiator() {
return differentiator2;
}
})));
assertThat(e.getMessage(), containsString("Cannot have more than one plugin providing a request cache key differentiator"));
}

private static final String[] NON_DEPRECATED_QUERIES = new String[] {
"bool",
"boosting",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.cluster.metadata.AliasMetadata;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.CheckedBiConsumer;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.common.xcontent.DeprecationHandler;
Expand All @@ -35,12 +37,15 @@

import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;

public class ShardSearchRequestTests extends AbstractSearchTestCase {
private static final IndexMetadata BASE_METADATA = IndexMetadata.builder("test").settings(Settings.builder()
Expand Down Expand Up @@ -147,7 +152,7 @@ private static void assertEquals(ShardSearchRequest orig, ShardSearchRequest cop
assertEquals(orig.searchType(), copy.searchType());
assertEquals(orig.shardId(), copy.shardId());
assertEquals(orig.numberOfShards(), copy.numberOfShards());
assertEquals(orig.cacheKey(), copy.cacheKey());
assertEquals(orig.cacheKey(null), copy.cacheKey(null));
assertNotSame(orig, copy);
assertEquals(orig.getAliasFilter(), copy.getAliasFilter());
assertEquals(orig.indexBoost(), copy.indexBoost(), 0.0f);
Expand Down Expand Up @@ -198,4 +203,15 @@ public void testChannelVersion() throws Exception {
}
}
}

public void testWillCallRequestCacheKeyDifferentiators() throws IOException {
final ShardSearchRequest shardSearchRequest = createShardSearchRequest();
final AtomicBoolean invoked = new AtomicBoolean(false);
final CheckedBiConsumer<ShardSearchRequest, StreamOutput, IOException> differentiator = (r, o) -> {
assertThat(r, sameInstance(shardSearchRequest));
invoked.set(true);
};
shardSearchRequest.cacheKey(differentiator);
assertThat(invoked.get(), is(true));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1772,7 +1772,8 @@ protected void assertSnapshotOrGenericThread() {
null,
emptyMap(),
List.of(),
emptyMap()
emptyMap(),
null
);
final RecoverySettings recoverySettings = new RecoverySettings(settings, clusterSettings);
snapshotShardsService = new SnapshotShardsService(
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ tasks.named("yamlRestCompatTest").configure {
'vectors/30_sparse_vector_basic/Dot Product',
'vectors/35_sparse_vector_l1l2/L1 norm',
'vectors/35_sparse_vector_l1l2/L2 norm',
'privileges/40_get_user_privs/Test get_user_privileges for merged roles', // temporary disabled till #70191 gets backported
'vectors/40_sparse_vector_special_cases/Dimensions can be sorted differently',
'vectors/40_sparse_vector_special_cases/Documents missing a vector field',
'vectors/40_sparse_vector_special_cases/Query vector has different dimensions from documents\' vectors',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
Expand Down Expand Up @@ -209,7 +210,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field(RoleDescriptor.Fields.PRIVILEGES.getPreferredName(), privileges);
if (fieldSecurity.stream().anyMatch(g -> nonEmpty(g.getGrantedFields()) || nonEmpty(g.getExcludedFields()))) {
builder.startArray(RoleDescriptor.Fields.FIELD_PERMISSIONS.getPreferredName());
for (FieldPermissionsDefinition.FieldGrantExcludeGroup group : this.fieldSecurity) {
final List<FieldPermissionsDefinition.FieldGrantExcludeGroup> sortedFieldSecurity =
this.fieldSecurity.stream().sorted().collect(Collectors.toUnmodifiableList());
for (FieldPermissionsDefinition.FieldGrantExcludeGroup group : sortedFieldSecurity) {
builder.startObject();
if (nonEmpty(group.getGrantedFields())) {
builder.array(RoleDescriptor.Fields.GRANT_FIELDS.getPreferredName(), group.getGrantedFields());
Expand Down
Loading