Skip to content

Commit 1017504

Browse files
committed
[ML] Refactor common utils out of ML plugin to XPack.Core (elastic#39976)
* [ML] Refactor common utils out of ML plugin to XPack.Core * implementing GET filters with abstract transport * removing added rest param * adjusting how defaults can be supplied
1 parent 8a314a3 commit 1017504

File tree

82 files changed

+739
-839
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+739
-839
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.core.action;
7+
8+
import org.elasticsearch.action.ActionRequest;
9+
import org.elasticsearch.action.ActionRequestValidationException;
10+
import org.elasticsearch.common.io.stream.StreamInput;
11+
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.xpack.core.action.util.PageParams;
13+
14+
import java.io.IOException;
15+
import java.util.Objects;
16+
17+
public abstract class AbstractGetResourcesRequest extends ActionRequest {
18+
19+
private String resourceId;
20+
private PageParams pageParams = PageParams.defaultParams();
21+
private boolean allowNoResources = false;
22+
23+
public AbstractGetResourcesRequest() {
24+
}
25+
26+
// Allow child classes to provide their own defaults if necessary
27+
protected AbstractGetResourcesRequest(String resourceId, PageParams pageParams, boolean allowNoResources) {
28+
this.resourceId = resourceId;
29+
this.pageParams = pageParams;
30+
this.allowNoResources = allowNoResources;
31+
}
32+
33+
public final void setResourceId(String resourceId) {
34+
this.resourceId = resourceId;
35+
}
36+
37+
public final String getResourceId() {
38+
return resourceId;
39+
}
40+
41+
public final void setPageParams(PageParams pageParams) {
42+
this.pageParams = pageParams;
43+
}
44+
45+
public final PageParams getPageParams() {
46+
return pageParams;
47+
}
48+
49+
public final void setAllowNoResources(boolean allowNoResources) {
50+
this.allowNoResources = allowNoResources;
51+
}
52+
53+
public final boolean isAllowNoResources() {
54+
return this.allowNoResources;
55+
}
56+
57+
@Override
58+
public ActionRequestValidationException validate() {
59+
return null;
60+
}
61+
62+
@Override
63+
public void readFrom(StreamInput in) throws IOException {
64+
super.readFrom(in);
65+
resourceId = in.readOptionalString();
66+
pageParams = in.readOptionalWriteable(PageParams::new);
67+
allowNoResources = in.readBoolean();
68+
}
69+
70+
@Override
71+
public void writeTo(StreamOutput out) throws IOException {
72+
super.writeTo(out);
73+
out.writeOptionalString(resourceId);
74+
out.writeOptionalWriteable(pageParams);
75+
out.writeBoolean(allowNoResources);
76+
}
77+
78+
@Override
79+
public int hashCode() {
80+
return Objects.hash(resourceId, pageParams, allowNoResources);
81+
}
82+
83+
@Override
84+
public boolean equals(Object obj) {
85+
if (obj == null) {
86+
return false;
87+
}
88+
if (obj instanceof AbstractGetResourcesRequest == false) {
89+
return false;
90+
}
91+
AbstractGetResourcesRequest other = (AbstractGetResourcesRequest) obj;
92+
return Objects.equals(resourceId, other.resourceId) &&
93+
Objects.equals(pageParams, other.pageParams) &&
94+
allowNoResources == other.allowNoResources;
95+
}
96+
97+
public abstract String getResourceIdField();
98+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.core.action;
7+
8+
import org.elasticsearch.action.ActionResponse;
9+
import org.elasticsearch.common.Strings;
10+
import org.elasticsearch.common.io.stream.StreamInput;
11+
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.common.io.stream.Writeable;
13+
import org.elasticsearch.common.xcontent.StatusToXContentObject;
14+
import org.elasticsearch.common.xcontent.ToXContent;
15+
import org.elasticsearch.common.xcontent.XContentBuilder;
16+
import org.elasticsearch.rest.RestStatus;
17+
import org.elasticsearch.xpack.core.action.util.QueryPage;
18+
19+
import java.io.IOException;
20+
import java.util.Objects;
21+
22+
public abstract class AbstractGetResourcesResponse<T extends ToXContent & Writeable> extends ActionResponse
23+
implements StatusToXContentObject {
24+
25+
private QueryPage<T> resources;
26+
27+
protected AbstractGetResourcesResponse() {}
28+
29+
protected AbstractGetResourcesResponse(QueryPage<T> resources) {
30+
this.resources = Objects.requireNonNull(resources);
31+
}
32+
33+
public QueryPage<T> getResources() {
34+
return resources;
35+
}
36+
37+
@Override
38+
public void readFrom(StreamInput in) throws IOException {
39+
super.readFrom(in);
40+
resources = new QueryPage<>(in, getReader());
41+
}
42+
43+
@Override
44+
public void writeTo(StreamOutput out) throws IOException {
45+
super.writeTo(out);
46+
resources.writeTo(out);
47+
}
48+
49+
@Override
50+
public RestStatus status() {
51+
return RestStatus.OK;
52+
}
53+
54+
@Override
55+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
56+
builder.startObject();
57+
resources.doXContentBody(builder, params);
58+
builder.endObject();
59+
return builder;
60+
}
61+
62+
@Override
63+
public int hashCode() {
64+
return Objects.hash(resources);
65+
}
66+
67+
@Override
68+
public boolean equals(Object obj) {
69+
if (obj == null) {
70+
return false;
71+
}
72+
if (obj instanceof AbstractGetResourcesResponse == false) {
73+
return false;
74+
}
75+
AbstractGetResourcesResponse other = (AbstractGetResourcesResponse) obj;
76+
return Objects.equals(resources, other.resources);
77+
}
78+
79+
@Override
80+
public final String toString() {
81+
return Strings.toString(this);
82+
}
83+
protected abstract Reader<T> getReader();
84+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.core.action;
7+
8+
import org.elasticsearch.ResourceNotFoundException;
9+
import org.elasticsearch.action.ActionListener;
10+
import org.elasticsearch.action.search.SearchRequest;
11+
import org.elasticsearch.action.search.SearchResponse;
12+
import org.elasticsearch.action.support.ActionFilters;
13+
import org.elasticsearch.action.support.HandledTransportAction;
14+
import org.elasticsearch.action.support.IndicesOptions;
15+
import org.elasticsearch.client.Client;
16+
import org.elasticsearch.common.Nullable;
17+
import org.elasticsearch.common.ParseField;
18+
import org.elasticsearch.common.Strings;
19+
import org.elasticsearch.common.bytes.BytesReference;
20+
import org.elasticsearch.common.io.stream.Writeable;
21+
import org.elasticsearch.common.regex.Regex;
22+
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
23+
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
24+
import org.elasticsearch.common.xcontent.ToXContent;
25+
import org.elasticsearch.common.xcontent.XContentFactory;
26+
import org.elasticsearch.common.xcontent.XContentParser;
27+
import org.elasticsearch.common.xcontent.XContentType;
28+
import org.elasticsearch.index.query.BoolQueryBuilder;
29+
import org.elasticsearch.index.query.QueryBuilder;
30+
import org.elasticsearch.index.query.QueryBuilders;
31+
import org.elasticsearch.search.SearchHit;
32+
import org.elasticsearch.search.builder.SearchSourceBuilder;
33+
import org.elasticsearch.transport.TransportService;
34+
import org.elasticsearch.xpack.core.action.util.ExpandedIdsMatcher;
35+
import org.elasticsearch.xpack.core.action.util.QueryPage;
36+
37+
import java.io.IOException;
38+
import java.io.InputStream;
39+
import java.util.ArrayList;
40+
import java.util.HashSet;
41+
import java.util.List;
42+
import java.util.Objects;
43+
import java.util.Set;
44+
import java.util.function.Supplier;
45+
46+
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
47+
48+
/**
49+
* Abstract transport class for collecting common logic in gathering Resource objects from indices
50+
* @param <Resource> The type of the Resource being gathered
51+
* @param <Request> The type of the Request
52+
* @param <Response> The type of the Response
53+
*/
54+
public abstract class AbstractTransportGetResourcesAction<Resource extends ToXContent & Writeable,
55+
Request extends AbstractGetResourcesRequest, Response extends AbstractGetResourcesResponse<Resource>>
56+
extends HandledTransportAction<Request, Response> {
57+
58+
private static final String ALL = "_all";
59+
60+
private final Client client;
61+
private final NamedXContentRegistry xContentRegistry;
62+
63+
protected AbstractTransportGetResourcesAction(String actionName, TransportService transportService, ActionFilters actionFilters,
64+
Supplier<Request> request, Client client, NamedXContentRegistry xContentRegistry) {
65+
super(actionName, transportService, actionFilters, request);
66+
this.client = Objects.requireNonNull(client);
67+
this.xContentRegistry = Objects.requireNonNull(xContentRegistry);
68+
}
69+
70+
protected void searchResources(AbstractGetResourcesRequest request, ActionListener<QueryPage<Resource>> listener) {
71+
String[] tokens = Strings.tokenizeToStringArray(request.getResourceId(), ",");
72+
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
73+
.sort(request.getResourceIdField())
74+
.query(buildQuery(tokens, request.getResourceIdField()));
75+
if (request.getPageParams() != null) {
76+
sourceBuilder.from(request.getPageParams().getFrom())
77+
.size(request.getPageParams().getSize());
78+
}
79+
80+
IndicesOptions indicesOptions = SearchRequest.DEFAULT_INDICES_OPTIONS;
81+
SearchRequest searchRequest = new SearchRequest(getIndices())
82+
.indicesOptions(IndicesOptions.fromOptions(true,
83+
indicesOptions.allowNoIndices(),
84+
indicesOptions.expandWildcardsOpen(),
85+
indicesOptions.expandWildcardsClosed(),
86+
indicesOptions))
87+
.source(sourceBuilder);
88+
89+
executeAsyncWithOrigin(client.threadPool().getThreadContext(),
90+
executionOrigin(),
91+
searchRequest,
92+
new ActionListener<SearchResponse>() {
93+
@Override
94+
public void onResponse(SearchResponse response) {
95+
List<Resource> docs = new ArrayList<>();
96+
Set<String> foundResourceIds = new HashSet<>();
97+
for (SearchHit hit : response.getHits().getHits()) {
98+
BytesReference docSource = hit.getSourceRef();
99+
try (InputStream stream = docSource.streamInput();
100+
XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(
101+
xContentRegistry, LoggingDeprecationHandler.INSTANCE, stream)) {
102+
Resource resource = parse(parser);
103+
docs.add(resource);
104+
foundResourceIds.add(extractIdFromResource(resource));
105+
} catch (IOException e) {
106+
this.onFailure(e);
107+
}
108+
}
109+
ExpandedIdsMatcher requiredMatches = new ExpandedIdsMatcher(tokens, request.isAllowNoResources());
110+
requiredMatches.filterMatchedIds(foundResourceIds);
111+
if (requiredMatches.hasUnmatchedIds()) {
112+
listener.onFailure(notFoundException(requiredMatches.unmatchedIdsString()));
113+
} else {
114+
listener.onResponse(new QueryPage<>(docs, docs.size(), getResultsField()));
115+
}
116+
}
117+
118+
119+
@Override
120+
public void onFailure(Exception e) {
121+
listener.onFailure(e);
122+
}
123+
},
124+
client::search);
125+
}
126+
127+
private QueryBuilder buildQuery(String[] tokens, String resourceIdField) {
128+
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
129+
130+
// If the resourceId is not _all or *, we should see if it is a comma delimited string with wild-cards
131+
// e.g. id1,id2*,id3
132+
if (Strings.isAllOrWildcard(tokens) == false) {
133+
BoolQueryBuilder shouldQueries = new BoolQueryBuilder();
134+
List<String> terms = new ArrayList<>();
135+
for (String token : tokens) {
136+
if (Regex.isSimpleMatchPattern(token)) {
137+
shouldQueries.should(QueryBuilders.wildcardQuery(resourceIdField, token));
138+
} else {
139+
terms.add(token);
140+
}
141+
}
142+
if (terms.isEmpty() == false) {
143+
shouldQueries.should(QueryBuilders.termsQuery(resourceIdField, terms));
144+
}
145+
146+
if (shouldQueries.should().isEmpty() == false) {
147+
boolQuery.filter(shouldQueries);
148+
}
149+
}
150+
QueryBuilder additionalQuery = additionalQuery();
151+
if (additionalQuery != null) {
152+
boolQuery.filter(additionalQuery);
153+
}
154+
return boolQuery.hasClauses() ? boolQuery : QueryBuilders.matchAllQuery();
155+
}
156+
157+
@Nullable
158+
protected QueryBuilder additionalQuery() {
159+
return null;
160+
}
161+
162+
/**
163+
* @return The results field parse field so that the response is properly formatted
164+
*/
165+
protected abstract ParseField getResultsField();
166+
167+
/**
168+
* @return The indices needed to query
169+
*/
170+
protected abstract String[] getIndices();
171+
172+
/**
173+
* @param parser Constructed XContentParser from search response hits to relay to a parser for the Resource
174+
* @return parsed Resource typed object
175+
*/
176+
protected abstract Resource parse(XContentParser parser) throws IOException;
177+
/**
178+
* @param resourceId Resource ID or expression that was not found in the search results
179+
* @return The exception to throw in the event that an ID or expression is not found
180+
*/
181+
protected abstract ResourceNotFoundException notFoundException(String resourceId);
182+
183+
/**
184+
* @return The appropriate origin under which to execute the search requests
185+
*/
186+
protected abstract String executionOrigin();
187+
188+
/**
189+
* @param resource A parsed Resource object
190+
* @return The ID of the resource
191+
*/
192+
protected abstract String extractIdFromResource(Resource resource);
193+
}

0 commit comments

Comments
 (0)