Skip to content

Commit b2d18ca

Browse files
authored
SOLR-16396: Convert v2 configset APIs to JAX-RS (#2928)
Convert v2 configset APIs to JAX-RS. Naturally this adds the APIs to Solr's growing v2 OAS, and ensures the APIs are included in code-gen artifacts. Also makes slight tweaks to line APIs up with the more REST-ful design chosen for other v2 APIs. - 'clone' (i.e. 'create-from-existing') is now available at `POST /api/configsets {...}` - 'delete' is now available at `DELETE /api/configsets/csName` - 'list' is now available at `GET /api/configsets`
1 parent d0897fc commit b2d18ca

File tree

15 files changed

+315
-312
lines changed

15 files changed

+315
-312
lines changed

solr/CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ Improvements
154154
specific collections or cores. Collection information can be fetched by a call to `GET /api/collections/collectionName`, and core
155155
information with a call to `GET /api/cores/coreName/segments`. (Jason Gerlowski)
156156

157+
* SOLR-16396: All v2 configset APIs have been moved to the slightly different path: `/api/configsets`, to better align with the design of
158+
other v2 APIs. SolrJ now offers (experimental) SolrRequest implementations for all v2 configset APIs in
159+
`org.apache.solr.client.solrj.request.ConfigsetsApi`. (Jason Gerlowski)
160+
157161
Optimizations
158162
---------------------
159163
* SOLR-17578: Remove ZkController internal core supplier, for slightly faster reconnection after Zookeeper session loss. (Pierre Salagnac)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.solr.client.api.endpoint;
18+
19+
import io.swagger.v3.oas.annotations.Operation;
20+
import io.swagger.v3.oas.annotations.parameters.RequestBody;
21+
import jakarta.ws.rs.DELETE;
22+
import jakarta.ws.rs.GET;
23+
import jakarta.ws.rs.POST;
24+
import jakarta.ws.rs.PUT;
25+
import jakarta.ws.rs.Path;
26+
import jakarta.ws.rs.PathParam;
27+
import jakarta.ws.rs.QueryParam;
28+
import java.io.IOException;
29+
import java.io.InputStream;
30+
import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
31+
import org.apache.solr.client.api.model.ListConfigsetsResponse;
32+
import org.apache.solr.client.api.model.SolrJerseyResponse;
33+
34+
public interface ConfigsetsApi {
35+
36+
/** V2 API definition for listing the configsets available to this SolrCloud cluster. */
37+
@Path("/configsets")
38+
interface List {
39+
@GET
40+
@Operation(
41+
summary = "List the configsets available to Solr.",
42+
tags = {"configsets"})
43+
ListConfigsetsResponse listConfigSet() throws Exception;
44+
}
45+
46+
/**
47+
* V2 API definition for creating a (possibly slightly modified) copy of an existing configset
48+
*
49+
* <p>Equivalent to the existing v1 API /admin/configs?action=CREATE
50+
*/
51+
@Path("/configsets")
52+
interface Clone {
53+
@POST
54+
@Operation(
55+
summary = "Create a new configset modeled on an existing one.",
56+
tags = {"configsets"})
57+
SolrJerseyResponse cloneExistingConfigSet(CloneConfigsetRequestBody requestBody)
58+
throws Exception;
59+
}
60+
61+
/**
62+
* V2 API definition for deleting an existing configset.
63+
*
64+
* <p>Equivalent to the existing v1 API /admin/configs?action=DELETE
65+
*/
66+
@Path("/configsets/{configSetName}")
67+
interface Delete {
68+
@DELETE
69+
@Operation(summary = "Delete an existing configset.", tags = "configsets")
70+
SolrJerseyResponse deleteConfigSet(@PathParam("configSetName") String configSetName)
71+
throws Exception;
72+
}
73+
74+
/**
75+
* V2 API definitions for uploading a configset, in whole or part.
76+
*
77+
* <p>Equivalent to the existing v1 API /admin/configs?action=UPLOAD
78+
*/
79+
@Path("/configsets/{configSetName}")
80+
interface Upload {
81+
@PUT
82+
@Operation(summary = "Create a new configset.", tags = "configsets")
83+
SolrJerseyResponse uploadConfigSet(
84+
@PathParam("configSetName") String configSetName,
85+
@QueryParam("overwrite") Boolean overwrite,
86+
@QueryParam("cleanup") Boolean cleanup,
87+
@RequestBody(required = true) InputStream requestBody)
88+
throws IOException;
89+
90+
@PUT
91+
@Path("{filePath:.+}")
92+
SolrJerseyResponse uploadConfigSetFile(
93+
@PathParam("configSetName") String configSetName,
94+
@PathParam("filePath") String filePath,
95+
@QueryParam("overwrite") Boolean overwrite,
96+
@QueryParam("cleanup") Boolean cleanup,
97+
@RequestBody(required = true) InputStream requestBody)
98+
throws IOException;
99+
}
100+
}

solr/api/src/java/org/apache/solr/client/api/endpoint/ListConfigsetsApi.java

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,20 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
package org.apache.solr.client.solrj.request.beans;
17+
package org.apache.solr.client.api.model;
1818

19+
import com.fasterxml.jackson.annotation.JsonProperty;
1920
import java.util.Map;
20-
import org.apache.solr.common.annotation.JsonProperty;
21-
import org.apache.solr.common.util.ReflectMapWriter;
2221

23-
public class CreateConfigPayload implements ReflectMapWriter {
24-
public static final String DEFAULT_CONFIGSET =
25-
"_default"; // TODO Better location for this in SolrJ?
22+
/** Request body for ConfigsetsApi.Clone */
23+
public class CloneConfigsetRequestBody {
24+
public static final String DEFAULT_CONFIGSET = "_default";
2625

2726
@JsonProperty(required = true)
2827
public String name;
2928

30-
@JsonProperty public String baseConfigSet = DEFAULT_CONFIGSET;
29+
@JsonProperty(defaultValue = DEFAULT_CONFIGSET)
30+
public String baseConfigSet;
31+
3132
@JsonProperty public Map<String, Object> properties;
3233
}

solr/core/src/java/org/apache/solr/handler/admin/ConfigSetsHandler.java

Lines changed: 36 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,30 @@
1717
package org.apache.solr.handler.admin;
1818

1919
import static org.apache.solr.common.params.CommonParams.NAME;
20-
import static org.apache.solr.handler.configsets.UploadConfigSetFileAPI.FILEPATH_PLACEHOLDER;
2120

2221
import java.lang.invoke.MethodHandles;
2322
import java.util.ArrayList;
2423
import java.util.Collection;
2524
import java.util.HashMap;
2625
import java.util.List;
27-
import java.util.Map;
28-
import org.apache.solr.api.AnnotatedApi;
2926
import org.apache.solr.api.Api;
3027
import org.apache.solr.api.JerseyResource;
31-
import org.apache.solr.api.PayloadObj;
32-
import org.apache.solr.client.solrj.request.beans.CreateConfigPayload;
28+
import org.apache.solr.client.api.model.CloneConfigsetRequestBody;
29+
import org.apache.solr.client.api.model.SolrJerseyResponse;
3330
import org.apache.solr.cloud.ConfigSetCmds;
3431
import org.apache.solr.common.SolrException;
3532
import org.apache.solr.common.SolrException.ErrorCode;
3633
import org.apache.solr.common.params.ConfigSetParams;
3734
import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
38-
import org.apache.solr.common.params.DefaultSolrParams;
39-
import org.apache.solr.common.params.ModifiableSolrParams;
4035
import org.apache.solr.common.params.SolrParams;
4136
import org.apache.solr.core.CoreContainer;
4237
import org.apache.solr.handler.RequestHandlerBase;
4338
import org.apache.solr.handler.api.V2ApiUtils;
44-
import org.apache.solr.handler.configsets.CreateConfigSetAPI;
45-
import org.apache.solr.handler.configsets.DeleteConfigSetAPI;
39+
import org.apache.solr.handler.configsets.CloneConfigSet;
40+
import org.apache.solr.handler.configsets.ConfigSetAPIBase;
41+
import org.apache.solr.handler.configsets.DeleteConfigSet;
4642
import org.apache.solr.handler.configsets.ListConfigSets;
47-
import org.apache.solr.handler.configsets.UploadConfigSetAPI;
48-
import org.apache.solr.handler.configsets.UploadConfigSetFileAPI;
49-
import org.apache.solr.request.DelegatingSolrQueryRequest;
43+
import org.apache.solr.handler.configsets.UploadConfigSet;
5044
import org.apache.solr.request.SolrQueryRequest;
5145
import org.apache.solr.response.SolrQueryResponse;
5246
import org.apache.solr.security.AuthorizationContext;
@@ -96,51 +90,30 @@ public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throw
9690

9791
switch (action) {
9892
case DELETE:
99-
final DeleteConfigSetAPI deleteConfigSetAPI = new DeleteConfigSetAPI(coreContainer);
100-
final SolrQueryRequest v2DeleteReq =
101-
new DelegatingSolrQueryRequest(req) {
102-
@Override
103-
public Map<String, String> getPathTemplateValues() {
104-
return Map.of(
105-
DeleteConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER,
106-
req.getParams().required().get(NAME));
107-
}
108-
};
109-
deleteConfigSetAPI.deleteConfigSet(v2DeleteReq, rsp);
93+
final DeleteConfigSet deleteConfigSetAPI = new DeleteConfigSet(coreContainer, req, rsp);
94+
final var deleteResponse =
95+
deleteConfigSetAPI.deleteConfigSet(req.getParams().required().get(NAME));
96+
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteResponse);
11097
break;
11198
case UPLOAD:
112-
final SolrQueryRequest v2UploadReq =
113-
new DelegatingSolrQueryRequest(req) {
114-
@Override
115-
public Map<String, String> getPathTemplateValues() {
116-
final Map<String, String> templateValsByName = new HashMap<>();
117-
118-
templateValsByName.put(
119-
UploadConfigSetAPI.CONFIGSET_NAME_PLACEHOLDER,
120-
req.getParams().required().get(NAME));
121-
if (!req.getParams().get(ConfigSetParams.FILE_PATH, "").isEmpty()) {
122-
templateValsByName.put(
123-
FILEPATH_PLACEHOLDER, req.getParams().get(ConfigSetParams.FILE_PATH));
124-
}
125-
return templateValsByName;
126-
}
127-
128-
// Set the v1 default vals where they differ from v2's
129-
@Override
130-
public SolrParams getParams() {
131-
final ModifiableSolrParams v1Defaults = new ModifiableSolrParams();
132-
v1Defaults.add(ConfigSetParams.OVERWRITE, "false");
133-
v1Defaults.add(ConfigSetParams.CLEANUP, "false");
134-
return new DefaultSolrParams(super.getParams(), v1Defaults);
135-
}
136-
};
99+
final var uploadApi = new UploadConfigSet(coreContainer, req, rsp);
100+
final var configSetName = req.getParams().required().get(NAME);
101+
final var overwrite = req.getParams().getBool(ConfigSetParams.OVERWRITE, false);
102+
final var cleanup = req.getParams().getBool(ConfigSetParams.CLEANUP, false);
103+
final var configSetData = ConfigSetAPIBase.ensureNonEmptyInputStream(req);
104+
SolrJerseyResponse uploadResponse;
137105
if (req.getParams()
138106
.get(ConfigSetParams.FILE_PATH, "")
139107
.isEmpty()) { // Uploading a whole configset
140-
new UploadConfigSetAPI(coreContainer).uploadConfigSet(v2UploadReq, rsp);
108+
uploadResponse =
109+
uploadApi.uploadConfigSet(configSetName, overwrite, cleanup, configSetData);
141110
} else { // Uploading a single file
142-
new UploadConfigSetFileAPI(coreContainer).updateConfigSetFile(v2UploadReq, rsp);
111+
final var filePath = req.getParams().get(ConfigSetParams.FILE_PATH);
112+
uploadResponse =
113+
uploadApi.uploadConfigSetFile(
114+
configSetName, filePath, overwrite, cleanup, configSetData);
143115
}
116+
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, uploadResponse);
144117
break;
145118
case LIST:
146119
final ListConfigSets listConfigSetsAPI = new ListConfigSets(coreContainer);
@@ -153,12 +126,14 @@ public SolrParams getParams() {
153126
}
154127

155128
// Map v1 parameters into v2 format and process request
156-
final CreateConfigPayload createPayload = new CreateConfigPayload();
157-
createPayload.name = newConfigSetName;
129+
final var requestBody = new CloneConfigsetRequestBody();
130+
requestBody.name = newConfigSetName;
158131
if (req.getParams().get(ConfigSetCmds.BASE_CONFIGSET) != null) {
159-
createPayload.baseConfigSet = req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
132+
requestBody.baseConfigSet = req.getParams().get(ConfigSetCmds.BASE_CONFIGSET);
133+
} else {
134+
requestBody.baseConfigSet = "_default";
160135
}
161-
createPayload.properties = new HashMap<>();
136+
requestBody.properties = new HashMap<>();
162137
req.getParams().stream()
163138
.filter(entry -> entry.getKey().startsWith(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX))
164139
.forEach(
@@ -167,10 +142,11 @@ public SolrParams getParams() {
167142
entry.getKey().substring(ConfigSetCmds.CONFIG_SET_PROPERTY_PREFIX.length());
168143
final Object value =
169144
(entry.getValue().length == 1) ? entry.getValue()[0] : entry.getValue();
170-
createPayload.properties.put(newKey, value);
145+
requestBody.properties.put(newKey, value);
171146
});
172-
final CreateConfigSetAPI createConfigSetAPI = new CreateConfigSetAPI(coreContainer);
173-
createConfigSetAPI.create(new PayloadObj<>("create", null, createPayload, req, rsp));
147+
final CloneConfigSet createConfigSetAPI = new CloneConfigSet(coreContainer, req, rsp);
148+
final var createResponse = createConfigSetAPI.cloneExistingConfigSet(requestBody);
149+
V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createResponse);
174150
break;
175151
default:
176152
throw new IllegalStateException("Unexpected ConfigSetAction detected: " + action);
@@ -207,18 +183,13 @@ public Boolean registerV2() {
207183

208184
@Override
209185
public Collection<Api> getApis() {
210-
final List<Api> apis = new ArrayList<>();
211-
apis.addAll(AnnotatedApi.getApis(new CreateConfigSetAPI(coreContainer)));
212-
apis.addAll(AnnotatedApi.getApis(new DeleteConfigSetAPI(coreContainer)));
213-
apis.addAll(AnnotatedApi.getApis(new UploadConfigSetAPI(coreContainer)));
214-
apis.addAll(AnnotatedApi.getApis(new UploadConfigSetFileAPI(coreContainer)));
215-
216-
return apis;
186+
return new ArrayList<>();
217187
}
218188

219189
@Override
220190
public Collection<Class<? extends JerseyResource>> getJerseyResources() {
221-
return List.of(ListConfigSets.class);
191+
return List.of(
192+
ListConfigSets.class, CloneConfigSet.class, DeleteConfigSet.class, UploadConfigSet.class);
222193
}
223194

224195
@Override

0 commit comments

Comments
 (0)