Skip to content

Commit 9ead859

Browse files
authored
SOLR-12224: Add APIs to read collection properties (#4071)
Prior to this PR Solr allowed users to write collection properties but never read them. This commit adds two new APIs to serve this need: the first for listing all properties (`GET /api/collections/someCollName/properties`) and the second for fetching a single property by name (`GET /api/collections/someCollName/properties/somePropName`). Corresponding SolrJ "SolrRequest" and "SolrResponse" classes are also generated based on the OAS definition for these new APIs.
1 parent 715658f commit 9ead859

7 files changed

Lines changed: 227 additions & 16 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
2+
title: Create new v2 APIs for listing and reading collection properties ("collprops")
3+
type: added # added, changed, fixed, deprecated, removed, dependency_update, security, other
4+
authors:
5+
- name: Jason Gerlowski
6+
links:
7+
- name: SOLR-12224
8+
url: https://issues.apache.org/jira/browse/SOLR-12224

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,36 @@
1818

1919
import io.swagger.v3.oas.annotations.Operation;
2020
import jakarta.ws.rs.DELETE;
21+
import jakarta.ws.rs.GET;
2122
import jakarta.ws.rs.PUT;
2223
import jakarta.ws.rs.Path;
2324
import jakarta.ws.rs.PathParam;
25+
import org.apache.solr.client.api.model.GetCollectionPropertyResponse;
26+
import org.apache.solr.client.api.model.ListCollectionPropertiesResponse;
2427
import org.apache.solr.client.api.model.SolrJerseyResponse;
2528
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
2629

2730
/** V2 API definitions for modifying collection-level properties. */
28-
@Path("/collections/{collName}/properties/{propName}")
31+
@Path("/collections/{collName}/properties")
2932
public interface CollectionPropertyApi {
33+
@GET
34+
@Operation(
35+
summary = "List all properties for the specified collection",
36+
tags = {"collection-properties"})
37+
ListCollectionPropertiesResponse listCollectionProperties(@PathParam("collName") String collName)
38+
throws Exception;
39+
40+
@GET
41+
@Path("/{propName}")
42+
@Operation(
43+
summary = "Get the value of a specific collection property",
44+
tags = {"collection-properties"})
45+
GetCollectionPropertyResponse getCollectionProperty(
46+
@PathParam("collName") String collName, @PathParam("propName") String propName)
47+
throws Exception;
48+
3049
@PUT
50+
@Path("/{propName}")
3151
@Operation(
3252
summary = "Create or update a collection property",
3353
tags = {"collection-properties"})
@@ -38,6 +58,7 @@ SolrJerseyResponse createOrUpdateCollectionProperty(
3858
throws Exception;
3959

4060
@DELETE
61+
@Path("/{propName}")
4162
@Operation(
4263
summary = "Delete the specified collection property from the collection",
4364
tags = {"collection-properties"})
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty;
20+
import io.swagger.v3.oas.annotations.media.Schema;
21+
22+
/** The Response for the v2 "get collection property" API */
23+
public class GetCollectionPropertyResponse extends SolrJerseyResponse {
24+
25+
@Schema(description = "The value of the collection property.")
26+
@JsonProperty("value")
27+
public String value;
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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.model;
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty;
20+
import io.swagger.v3.oas.annotations.media.Schema;
21+
import java.util.Map;
22+
23+
/** The Response for the v2 "list collection properties" API */
24+
public class ListCollectionPropertiesResponse extends SolrJerseyResponse {
25+
26+
@Schema(description = "The properties for the collection.")
27+
@JsonProperty("properties")
28+
public Map<String, String> properties;
29+
}

solr/core/src/java/org/apache/solr/handler/admin/api/CollectionProperty.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
package org.apache.solr.handler.admin.api;
1919

2020
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
21+
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
2122

23+
import jakarta.inject.Inject;
2224
import java.io.IOException;
25+
import java.util.Map;
2326
import org.apache.solr.client.api.endpoint.CollectionPropertyApi;
27+
import org.apache.solr.client.api.model.GetCollectionPropertyResponse;
28+
import org.apache.solr.client.api.model.ListCollectionPropertiesResponse;
2429
import org.apache.solr.client.api.model.SolrJerseyResponse;
2530
import org.apache.solr.client.api.model.UpdateCollectionPropertyRequestBody;
2631
import org.apache.solr.common.SolrException;
@@ -31,20 +36,66 @@
3136
import org.apache.solr.response.SolrQueryResponse;
3237

3338
/**
34-
* V2 API implementations for modifying collection-level properties.
39+
* V2 API implementations for managing collection-level properties.
3540
*
36-
* <p>These APIs (PUT and DELETE /api/collections/collName/properties/propName) are analogous to the
37-
* v1 /admin/collections?action=COLLECTIONPROP command.
41+
* <p>These APIs are analogous to the v1 /admin/collections?action=COLLECTIONPROP command.
3842
*/
3943
public class CollectionProperty extends AdminAPIBase implements CollectionPropertyApi {
4044

45+
@Inject
4146
public CollectionProperty(
4247
CoreContainer coreContainer,
4348
SolrQueryRequest solrQueryRequest,
4449
SolrQueryResponse solrQueryResponse) {
4550
super(coreContainer, solrQueryRequest, solrQueryResponse);
4651
}
4752

53+
@Override
54+
@PermissionName(COLL_READ_PERM)
55+
public ListCollectionPropertiesResponse listCollectionProperties(String collName)
56+
throws Exception {
57+
final var response = instantiateJerseyResponse(ListCollectionPropertiesResponse.class);
58+
ensureRequiredParameterProvided("collName", collName);
59+
fetchAndValidateZooKeeperAwareCoreContainer();
60+
recordCollectionForLogAndTracing(collName, solrQueryRequest);
61+
62+
String resolvedCollection = coreContainer.getAliases().resolveSimpleAlias(collName);
63+
CollectionProperties cp =
64+
new CollectionProperties(coreContainer.getZkController().getZkClient());
65+
Map<String, String> properties = cp.getCollectionProperties(resolvedCollection);
66+
67+
// Handle null case - return empty map instead of null
68+
response.properties = (properties != null) ? properties : Map.of();
69+
70+
return response;
71+
}
72+
73+
@Override
74+
@PermissionName(COLL_READ_PERM)
75+
public GetCollectionPropertyResponse getCollectionProperty(String collName, String propName)
76+
throws Exception {
77+
final var response = instantiateJerseyResponse(GetCollectionPropertyResponse.class);
78+
ensureRequiredParameterProvided("collName", collName);
79+
ensureRequiredParameterProvided("propName", propName);
80+
fetchAndValidateZooKeeperAwareCoreContainer();
81+
recordCollectionForLogAndTracing(collName, solrQueryRequest);
82+
83+
String resolvedCollection = coreContainer.getAliases().resolveSimpleAlias(collName);
84+
CollectionProperties cp =
85+
new CollectionProperties(coreContainer.getZkController().getZkClient());
86+
Map<String, String> properties = cp.getCollectionProperties(resolvedCollection);
87+
88+
if (properties != null && properties.containsKey(propName)) {
89+
response.value = properties.get(propName);
90+
} else {
91+
throw new SolrException(
92+
SolrException.ErrorCode.NOT_FOUND,
93+
"Property '" + propName + "' not found for collection '" + collName + "'");
94+
}
95+
96+
return response;
97+
}
98+
4899
@Override
49100
@PermissionName(COLL_EDIT_PERM)
50101
public SolrJerseyResponse createOrUpdateCollectionProperty(

solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.apache.solr.client.solrj.SolrServerException;
5050
import org.apache.solr.client.solrj.impl.CloudSolrClient;
5151
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
52+
import org.apache.solr.client.solrj.request.CollectionPropertiesApi;
5253
import org.apache.solr.client.solrj.request.CollectionsApi;
5354
import org.apache.solr.client.solrj.request.CoreAdminRequest;
5455
import org.apache.solr.client.solrj.request.V2Request;
@@ -536,7 +537,7 @@ public void testClusterProp() throws IOException, SolrServerException {
536537
}
537538

538539
@Test
539-
public void testCollectionProp() throws InterruptedException, IOException, SolrServerException {
540+
public void testCollectionProp() throws Exception {
540541
String collectionName = getSaferTestName();
541542
final String propName = "testProperty";
542543

@@ -554,18 +555,43 @@ public void testCollectionProp() throws InterruptedException, IOException, SolrS
554555
CollectionAdminRequest.setCollectionProperty(collectionName, propName, null)
555556
.process(cluster.getSolrClient());
556557
checkCollectionProperty(collectionName, propName, null);
558+
559+
// Test that "list-properties" returns all properties
560+
CollectionAdminRequest.setCollectionProperty(collectionName, propName + "1", "prop1Val")
561+
.process(cluster.getSolrClient());
562+
CollectionAdminRequest.setCollectionProperty(collectionName, propName + "2", "prop2Val")
563+
.process(cluster.getSolrClient());
564+
final var allProperties =
565+
new CollectionPropertiesApi.ListCollectionProperties(collectionName)
566+
.process(cluster.getSolrClient())
567+
.properties;
568+
assertEquals(2, allProperties.size());
569+
assertEquals("prop1Val", allProperties.get(propName + "1"));
570+
assertEquals("prop2Val", allProperties.get(propName + "2"));
571+
572+
// Test GET single property API
573+
final var prop1Response =
574+
new CollectionPropertiesApi.GetCollectionProperty(collectionName, propName + "1")
575+
.process(cluster.getSolrClient());
576+
assertEquals("prop1Val", prop1Response.value);
577+
578+
final var prop2Response =
579+
new CollectionPropertiesApi.GetCollectionProperty(collectionName, propName + "2")
580+
.process(cluster.getSolrClient());
581+
assertEquals("prop2Val", prop2Response.value);
557582
}
558583

559584
private void checkCollectionProperty(String collection, String propertyName, String propertyValue)
560-
throws InterruptedException {
585+
throws Exception {
561586
TimeOut timeout = new TimeOut(TIMEOUT, TimeUnit.MILLISECONDS, TimeSource.NANO_TIME);
562587
while (!timeout.hasTimedOut()) {
563-
Thread.sleep(10);
564-
if (Objects.equals(
565-
cluster.getZkStateReader().getCollectionProperties(collection).get(propertyName),
566-
propertyValue)) {
588+
final var listCollPropRsp =
589+
new CollectionPropertiesApi.ListCollectionProperties(collection)
590+
.process(cluster.getSolrClient());
591+
if (Objects.equals(listCollPropRsp.properties.get(propertyName), propertyValue)) {
567592
return;
568593
}
594+
Thread.sleep(10);
569595
}
570596

571597
fail("Timed out waiting for cluster property value");

solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,8 @@ If the status is anything other than "success", an error message will explain wh
682682
[[collectionprop]]
683683
== COLLECTIONPROP: Collection Properties
684684

685-
Add, edit or delete a collection property.
685+
Add, update, delete, or retrieve collection properties.
686+
(Listing all collection properties, or fetching an individual property by name are only supported in Solr's v2 API)
686687

687688
[tabs#collectionproperty-request]
688689
======
@@ -691,28 +692,44 @@ V1 API::
691692
====
692693
[source,bash]
693694
----
694-
http://localhost:8983/solr/admin/collections?action=COLLECTIONPROP&name=techproducts_v2&propertyName=propertyName&propertyValue=propertyValue
695+
http://localhost:8983/solr/admin/collections?action=COLLECTIONPROP&name=techproducts&propertyName=propertyName&propertyValue=propertyValue
695696
----
696697
====
697698
698699
V2 API::
699700
+
700701
====
701702
To create or update a collection property:
703+
702704
[source,bash]
703705
----
704-
curl -X PUT http://localhost:8983/api/collections/techproducts_v2/properties/foo -H 'Content-Type: application/json' -d '
706+
curl -X PUT http://localhost:8983/api/collections/techproducts_v2/properties/propertyName -H 'Content-Type: application/json' -d '
705707
{
706-
"value": "bar"
708+
"value": "propertyValue"
707709
}
708710
'
709711
----
710712

713+
To list all properties for a collection:
714+
715+
[source,bash]
716+
----
717+
curl http://localhost:8983/api/collections/techproducts/properties
718+
----
719+
720+
To get a specific collection property by name:
721+
722+
[source,bash]
723+
----
724+
curl http://localhost:8983/api/collections/techproducts/properties/propertyName
725+
----
726+
727+
711728
To delete an existing collection property:
712729

713730
[source,bash]
714731
----
715-
curl -X DELETE http://localhost:8983/api/collections/techproducts_v2/properties/foo
732+
curl -X DELETE http://localhost:8983/api/collections/techproducts/properties/propertyName
716733
----
717734
====
718735
======
@@ -751,9 +768,40 @@ When not provided in v1 requests, the property is deleted.
751768

752769
=== COLLECTIONPROP Response
753770

754-
The response will include the status of the request and the properties that were updated or removed.
771+
The response will include the status of the request.
755772
If the status is anything other than "0", an error message will explain why the request failed.
756773

774+
For GET requests to list all properties, the response includes a `properties` object containing all property name-value pairs:
775+
776+
[source,json]
777+
----
778+
{
779+
"responseHeader": {
780+
"status": 0,
781+
"QTime": 5
782+
},
783+
"properties": {
784+
"foo": "bar",
785+
"myProperty": "myValue"
786+
}
787+
}
788+
----
789+
790+
For GET requests to retrieve a single property, the response includes a `value` field with the property value:
791+
792+
[source,json]
793+
----
794+
{
795+
"responseHeader": {
796+
"status": 0,
797+
"QTime": 3
798+
},
799+
"value": "bar"
800+
}
801+
----
802+
803+
If a requested property does not exist, the API will return a 404 error with an appropriate error message.
804+
757805
[[migrate]]
758806
== MIGRATE: Migrate Documents to Another Collection
759807

0 commit comments

Comments
 (0)