Skip to content

Commit

Permalink
SOLR-16835: Add approximate-/select to OAS (#2079)
Browse files Browse the repository at this point in the history
Now that Solr uses its OAS to generate client bindings in multiple
languages (Java and JavaScript, so far), users of these clients may wish
to use them to run searches.

Normally, this would require converting the `/select` endpoint to JAX-RS
so that its inputs and outputs can be described comprehensively in our
OAS.  However, doing this for `/select` will take some time due to the
complexity and large degree of configurability the endpoint offers.

This commit works around this limitation by creating an approximation of
the `/select` endpoint that can appear in our OAS until the API can be
converted to JAX-RS in earnest.  This will give generated-client users
access to at least some query functionality in this interim.

The `/select` query parameters supported in this commit were chosen
mostly arbitrarily.  They may be added to freely as generated-client
users run into particular needs (e.g. for setting 'faceting'
parameters).
  • Loading branch information
gerlowskija authored Nov 29, 2023
1 parent 3ba8346 commit fb9b6fd
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.client.api.endpoint;

import static org.apache.solr.client.api.util.Constants.GENERIC_ENTITY_PROPERTY;
import static org.apache.solr.client.api.util.Constants.STORE_PATH_PREFIX;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.extensions.Extension;
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import java.io.InputStream;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import org.apache.solr.client.api.model.FlexibleSolrJerseyResponse;
import org.apache.solr.client.api.util.StoreApiParameters;

/**
* V2 API implementation shim for Solr's querying or otherwise inspecting documents in a core or
* collection.
*
* <p>Due to the complexity and configurability of Solr's '/select' endpoint, this interface doesn't
* attempt to be exhaustive in describing /select inputs and outputs. Rather, it exists to give
* Solr's OAS (and the clients generated from that) an approximate view of the endpoint until its
* inputs and outputs can be understood more fully.
*/
@Path(STORE_PATH_PREFIX + "/select")
public interface SelectApi {
@GET
@StoreApiParameters
@Operation(
summary = "Query a Solr core or collection using individual query parameters",
tags = {"querying"})
FlexibleSolrJerseyResponse query(
@QueryParam("q") String query,
@QueryParam("fq") List<String> filterQueries,
@QueryParam("fl") String fieldList,
@QueryParam("rows") Integer rows);

@POST
@StoreApiParameters
@Operation(
summary = "Query a Solr core or collection using the structured request DSL",
tags = {"querying"})
// TODO Find way to bundle the request-body annotations below for re-use on other similar
// endpoints.
FlexibleSolrJerseyResponse jsonQuery(
@Parameter(required = true)
@RequestBody(
required = true,
extensions = {
@Extension(
properties = {
@ExtensionProperty(name = GENERIC_ENTITY_PROPERTY, value = "true")
})
})
InputStream structuredRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.client.api.model;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import java.util.HashMap;
import java.util.Map;

/** A {@link SolrJerseyResponse} which can accept any top-level properties. */
public class FlexibleSolrJerseyResponse extends SolrJerseyResponse {

private Map<String, Object> unknownFields = new HashMap<>();

@JsonAnyGetter
public Map<String, Object> unknownProperties() {
return unknownFields;
}

@JsonAnySetter
public void setUnknownProperty(String field, Object value) {
unknownFields.put(field, value);
}
}
15 changes: 13 additions & 2 deletions solr/api/src/java/org/apache/solr/client/api/model/StoreType.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@
package org.apache.solr.client.api.model;

public enum StoreType {
COLLECTION,
CORE
COLLECTION("collections"),
CORE("cores");

private final String pathString;

StoreType(String pathString) {
this.pathString = pathString;
}

@Override
public String toString() {
return pathString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ private Constants() {
public static final String STORE_PATH_PREFIX =
"/{" + STORE_TYPE_PATH_PARAMETER + ":cores|collections}/{" + STORE_NAME_PATH_PARAMETER + "}";

public static final String GENERIC_ENTITY_PROPERTY = "genericEntity";

public static final String BINARY_CONTENT_TYPE_V2 = "application/vnd.apache.solr.javabin";
}
33 changes: 29 additions & 4 deletions solr/solrj/src/resources/java-template/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ import {{modelPackage}}.{{dataType}};
// WARNING: This class is generated from a Mustache template; any intended
// changes should be made to the underlying template and not this file directly.

/**
* Experimental SolrRequest's and SolrResponse's for {{classVarName}}, generated from an OAS.
*
* <p>See individual request and response classes for more detailed and relevant information.
*
* <p>All SolrRequest implementations rely on v2 APIs which may require a SolrClient configured to
* use the '/api' path prefix, instead of '/solr'.
*
* @lucene.experimental
*/
public class {{classname}} {
{{#operation}}
Expand Down Expand Up @@ -87,21 +97,29 @@ public class {{classname}} {
{{#requiredParams}}{{^isBodyParam}}* @param {{paramName}} Path param - {{description}}{{/isBodyParam}}
{{/requiredParams}}
*/
public {{operationIdCamelCase}}({{#requiredParams}}{{^isBodyParam}}{{^-first}}, {{/-first}}{{{dataType}}} {{paramName}}{{/isBodyParam}}{{/requiredParams}}) {
public {{operationIdCamelCase}}({{#allParams}}{{#required}}{{^isBodyParam}}{{^-first}}, {{/-first}}{{{dataType}}} {{paramName}}{{/isBodyParam}}{{#isBodyParam}}{{#vendorExtensions.x-genericEntity}}{{^-first}}, {{/-first}}{{{dataType}}} requestBody{{/vendorExtensions.x-genericEntity}}{{/isBodyParam}}{{/required}}{{/allParams}}) {
super(
SolrRequest.METHOD.valueOf("{{httpMethod}}"),
"{{{path}}}"{{#pathParams}}
.replace("{" + "{{baseName}}" + "}", {{paramName}}{{#isEnumRef}}.name(){{/isEnumRef}}){{/pathParams}}
.replace("{" + "{{baseName}}" + "}", {{paramName}}{{^isString}}.toString(){{/isString}}){{/pathParams}}
);

{{#requiredParams}}
{{#isBodyParam}}
{{#vendorExtensions.x-genericEntity}}
this.requestBody = requestBody;
addHeader("Content-type", "application/json");
{{/vendorExtensions.x-genericEntity}}
{{/isBodyParam}}
{{^isBodyParam}}
this.{{paramName}} = {{paramName}};
{{/isBodyParam}}
{{/requiredParams}}
{{#bodyParam}}
this.requestBody = new {{baseName}}();
{{^vendorExtensions.x-genericEntity}}
this.requestBody = new {{{dataType}}}();
addHeader("Content-type", "application/json");
{{/vendorExtensions.x-genericEntity}}
{{/bodyParam}}
}

Expand Down Expand Up @@ -133,7 +151,13 @@ public class {{classname}} {
{{/vars}}

@Override
@SuppressWarnings("unchecked")
public RequestWriter.ContentWriter getContentWriter(String expectedType) {
{{#vendorExtensions.x-genericEntity}}
if (requestBody instanceof String) {
return new RequestWriter.StringPayloadContentWriter((String) requestBody, expectedType);
}
{{/vendorExtensions.x-genericEntity}}
return new JacksonContentWriter(expectedType, requestBody);
}
{{/bodyParam}}
Expand All @@ -149,7 +173,8 @@ public class {{classname}} {
final ModifiableSolrParams params = new ModifiableSolrParams();
{{#queryParams}}
if ({{paramName}} != null) {
params.add("{{baseName}}", {{paramName}}{{^isString}}.toString(){{/isString}});
{{#isArray}}{{paramName}}.stream().forEach(v -> params.add("{{baseName}}", v{{^items.isString}}.toString(){{/items.isString}}));{{/isArray}}
{{^isArray}}params.add("{{baseName}}", {{paramName}}{{^isString}}.toString(){{/isString}});{{/isArray}}
}
{{/queryParams}}
return params;
Expand Down

0 comments on commit fb9b6fd

Please sign in to comment.