This is an automated email from the ASF dual-hosted git repository.
jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push:
new 777796a3615 Encode column and segment names when constructing metadata
read URL (#16940)
777796a3615 is described below
commit 777796a36151d73bdc4f7b08e9ece55f213d1ee4
Author: Xiaotian (Jackie) Jiang <[email protected]>
AuthorDate: Fri Oct 3 11:22:37 2025 -0700
Encode column and segment names when constructing metadata read URL (#16940)
---
.../broker/api/resources/PinotBrokerRouting.java | 7 +-
.../controller/api/resources/DebugResource.java | 8 +-
.../api/resources/PinotSegmentRestletResource.java | 27 +++---
.../api/resources/PinotTableRestletResource.java | 4 +-
.../util/ServerSegmentMetadataReader.java | 69 +++++++--------
.../tests/HybridClusterIntegrationTest.java | 16 +---
.../tests/OfflineClusterIntegrationTest.java | 15 ----
.../server/api/resources/ReingestionResource.java | 5 +-
.../api/resources/SegmentMetadataFetcher.java | 8 +-
.../pinot/server/api/resources/TablesResource.java | 99 ++++++++--------------
.../utils/builder/ControllerRequestURLBuilder.java | 57 ++-----------
.../pinot/spi/utils/builder/UrlBuilderUtils.java | 75 ++++++++++++++++
12 files changed, 180 insertions(+), 210 deletions(-)
diff --git
a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java
b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java
index 546e155cf0c..dd55158e5ad 100644
---
a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java
+++
b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java
@@ -29,6 +29,7 @@ import io.swagger.annotations.SecurityDefinition;
import io.swagger.annotations.SwaggerDefinition;
import javax.inject.Inject;
import javax.ws.rs.DELETE;
+import javax.ws.rs.Encoded;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -40,6 +41,7 @@ import org.apache.helix.HelixManager;
import org.apache.pinot.broker.routing.BrokerRoutingManager;
import org.apache.pinot.common.metadata.ZKMetadataProvider;
import org.apache.pinot.common.utils.DatabaseUtils;
+import org.apache.pinot.common.utils.URIUtils;
import org.apache.pinot.core.auth.Actions;
import org.apache.pinot.core.auth.Authorize;
import org.apache.pinot.core.auth.TargetType;
@@ -97,9 +99,10 @@ public class PinotBrokerRouting {
})
public String refreshRouting(
@ApiParam(value = "Table name (with type)") @PathParam("tableName")
String tableNameWithType,
- @ApiParam(value = "Segment name") @PathParam("segmentName") String
segmentName,
+ @ApiParam(value = "Segment name") @PathParam("segmentName") @Encoded
String segmentName,
@Context HttpHeaders headers) {
-
_routingManager.refreshSegment(DatabaseUtils.translateTableName(tableNameWithType,
headers), segmentName);
+
_routingManager.refreshSegment(DatabaseUtils.translateTableName(tableNameWithType,
headers),
+ URIUtils.decode(segmentName));
return "Success";
}
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java
index 6a2235389c1..106bb3a4aef 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/DebugResource.java
@@ -42,6 +42,7 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
+import javax.ws.rs.Encoded;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -63,6 +64,7 @@ import
org.apache.pinot.common.restlet.resources.SegmentConsumerInfo;
import org.apache.pinot.common.restlet.resources.SegmentErrorInfo;
import org.apache.pinot.common.restlet.resources.SegmentServerDebugInfo;
import org.apache.pinot.common.utils.DatabaseUtils;
+import org.apache.pinot.common.utils.URIUtils;
import org.apache.pinot.controller.ControllerConf;
import org.apache.pinot.controller.api.debug.TableDebugInfo;
import
org.apache.pinot.controller.api.exception.ControllerApplicationException;
@@ -168,12 +170,12 @@ public class DebugResource {
})
public TableDebugInfo.SegmentDebugInfo getSegmentDebugInfo(
@ApiParam(value = "Name of the table (with type)", required = true)
@PathParam("tableName")
- String tableNameWithType,
- @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") String segmentName,
+ String tableNameWithType,
+ @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") @Encoded String segmentName,
@Context HttpHeaders headers)
throws Exception {
tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType,
headers);
- return debugSegment(tableNameWithType, segmentName);
+ return debugSegment(tableNameWithType, URIUtils.decode(segmentName));
}
/**
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java
index 33441ed9067..def5f3fa843 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotSegmentRestletResource.java
@@ -350,7 +350,7 @@ public class PinotSegmentRestletResource {
if (segmentMetadata != null) {
Map<String, Object> result = new HashMap<>(segmentMetadata);
- if (columns.size() > 0) {
+ if (!columns.isEmpty()) {
JsonNode segmentsMetadataJson = getExtraMetaData(tableName,
segmentName, columns);
if (segmentsMetadataJson.has("indexes")) {
result.put("indexes", segmentsMetadataJson.get("indexes"));
@@ -815,7 +815,7 @@ public class PinotSegmentRestletResource {
}
String tableNameWithType =
ResourceUtils.getExistingTableNamesWithType(_pinotHelixResourceManager,
tableName, tableType, LOGGER).get(0);
- if (segments == null || segments.isEmpty()) {
+ if (segments.isEmpty()) {
deleteSegmentsInternal(tableNameWithType,
_pinotHelixResourceManager.getSegmentsFromPropertyStore(tableNameWithType),
retentionPeriod);
return new SuccessResponse("All segments of table " + tableNameWithType
+ " deleted");
@@ -876,9 +876,9 @@ public class PinotSegmentRestletResource {
+ " specified in the segment lineage entries and cannot be queried
from the table, false by default")
@QueryParam("excludeReplacedSegments") @DefaultValue("false") boolean
excludeReplacedSegments,
@ApiParam(value = "Start timestamp (inclusive) in milliseconds",
required = true) @QueryParam("startTimestamp")
- String startTimestampStr,
+ String startTimestampStr,
@ApiParam(value = "End timestamp (exclusive) in milliseconds", required
= true) @QueryParam("endTimestamp")
- String endTimestampStr,
+ String endTimestampStr,
@ApiParam(value = "Whether to ignore segments that are partially
overlapping with the [start, end)"
+ "for deletion, true by default")
@QueryParam("excludeOverlapping") @DefaultValue("true") boolean
excludeOverlapping,
@@ -926,12 +926,13 @@ public class PinotSegmentRestletResource {
public String getServerMetadata(
@ApiParam(value = "Name of the table", required = true)
@PathParam("tableName") String tableName,
@ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String
tableTypeStr,
- @Encoded @ApiParam(value = "Segments to include (all if not specified)",
allowMultiple = true)
- @QueryParam("segments") @Nullable List<String> segments,
- @Encoded @ApiParam(value = "Columns name", allowMultiple = true)
@QueryParam("columns")
- @Nullable List<String> columns, @Context HttpHeaders headers) {
+ @ApiParam(value = "Segments to include (all if not specified)",
allowMultiple = true) @QueryParam("segments")
+ List<String> segments,
+ @ApiParam(value = "Columns to include (use '*' to include all)",
allowMultiple = true) @QueryParam("columns")
+ List<String> columns,
+ @Context HttpHeaders headers) {
tableName = DatabaseUtils.translateTableName(tableName, headers);
- String segmentCount = (segments == null) ? "all" :
String.valueOf(segments.size());
+ String segmentCount = segments.isEmpty() ? "all" :
String.valueOf(segments.size());
LOGGER.info("Received a request to fetch metadata for {} segments for
table {}", segmentCount, tableName);
TableType tableType = Constants.validateTableType(tableTypeStr);
@@ -1162,14 +1163,12 @@ public class PinotSegmentRestletResource {
* @param segments name of the segments to include in metadata
* @return Map<String, String> metadata of the table segments -> map of
segment name to its metadata
*/
- private JsonNode getSegmentsMetadataFromServer(String tableNameWithType,
@Nullable List<String> columns,
- @Nullable List<String> segments)
+ private JsonNode getSegmentsMetadataFromServer(String tableNameWithType,
List<String> columns, List<String> segments)
throws InvalidConfigException, IOException {
TableMetadataReader tableMetadataReader =
new TableMetadataReader(_executor, _connectionManager,
_pinotHelixResourceManager);
- return tableMetadataReader
- .getSegmentsMetadata(tableNameWithType, columns, segments,
- _controllerConf.getServerAdminRequestTimeoutSeconds() * 1000);
+ return tableMetadataReader.getSegmentsMetadata(tableNameWithType, columns,
segments,
+ _controllerConf.getServerAdminRequestTimeoutSeconds() * 1000);
}
@POST
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
index 7f52166d24c..505216602fb 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotTableRestletResource.java
@@ -1097,8 +1097,8 @@ public class PinotTableRestletResource {
public String getTableAggregateMetadata(
@ApiParam(value = "Name of the table", required = true)
@PathParam("tableName") String tableName,
@ApiParam(value = "OFFLINE|REALTIME") @QueryParam("type") String
tableTypeStr,
- @ApiParam(value = "Columns name", allowMultiple = true)
@QueryParam("columns") @DefaultValue("")
- List<String> columns, @Context HttpHeaders headers) {
+ @ApiParam(value = "Columns name", allowMultiple = true)
@QueryParam("columns") List<String> columns,
+ @Context HttpHeaders headers) {
tableName = DatabaseUtils.translateTableName(tableName, headers);
LOGGER.info("Received a request to fetch aggregate metadata for a table
{}", tableName);
TableType tableType = Constants.validateTableType(tableTypeStr);
diff --git
a/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java
b/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java
index c0ebfd5313c..53cf628c7fb 100644
---
a/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java
+++
b/pinot-controller/src/main/java/org/apache/pinot/controller/util/ServerSegmentMetadataReader.java
@@ -39,7 +39,6 @@ import javax.annotation.Nullable;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
-import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
@@ -51,6 +50,7 @@ import org.apache.pinot.common.utils.RoaringBitmapUtils;
import org.apache.pinot.controller.api.resources.TableStaleSegmentResponse;
import org.apache.pinot.segment.local.data.manager.StaleSegment;
import org.apache.pinot.spi.utils.JsonUtils;
+import org.apache.pinot.spi.utils.builder.UrlBuilderUtils;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.roaringbitmap.RoaringBitmap;
@@ -430,38 +430,41 @@ public class ServerSegmentMetadataReader {
return serverResponses;
}
- private String generateAggregateSegmentMetadataServerURL(String
tableNameWithType, List<String> columns,
+ private String generateAggregateSegmentMetadataServerURL(String
tableNameWithType, @Nullable List<String> columns,
String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
- String paramsStr = generateParam(COLUMNS_KEY, columns);
- return String.format("%s/tables/%s/metadata?%s", endpoint,
tableNameWithType, paramsStr);
+ tableNameWithType = encode(tableNameWithType);
+ String columnsParam = UrlBuilderUtils.generateColumnsParam(columns);
+ String url = String.format("%s/tables/%s/metadata", endpoint,
tableNameWithType);
+ return columnsParam != null ? url + "?" + columnsParam : url;
}
public String generateSegmentMetadataServerURL(String tableNameWithType,
String segmentName,
@Nullable List<String> columns, String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
- segmentName = URLEncoder.encode(segmentName, StandardCharsets.UTF_8);
- String paramsStr = generateParam(COLUMNS_KEY, columns);
- return String.format("%s/tables/%s/segments/%s/metadata?%s", endpoint,
tableNameWithType, segmentName, paramsStr);
+ tableNameWithType = encode(tableNameWithType);
+ segmentName = UrlBuilderUtils.encode(segmentName);
+ String columnsParam = UrlBuilderUtils.generateColumnsParam(columns);
+ String url = String.format("%s/tables/%s/segments/%s/metadata", endpoint,
tableNameWithType, segmentName);
+ return columnsParam != null ? url + "?" + columnsParam : url;
}
public String generateTableMetadataServerURL(String tableNameWithType,
@Nullable List<String> columns,
- @Nullable List<String> segmentsToInclude, String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
- String paramsStr = generateParam(COLUMNS_KEY, columns) + "&" +
generateParam(SEGMENTS_KEY, segmentsToInclude);
- return String.format("%s/tables/%s/segments/metadata?%s", endpoint,
tableNameWithType, paramsStr);
+ @Nullable List<String> segments, String endpoint) {
+ tableNameWithType = encode(tableNameWithType);
+ String columnsAndSegmentsParam =
UrlBuilderUtils.generateColumnsAndSegmentsParam(columns, segments);
+ String url = String.format("%s/tables/%s/segments/metadata", endpoint,
tableNameWithType);
+ return columnsAndSegmentsParam != null ? url + "?" +
columnsAndSegmentsParam : url;
}
private String generateCheckReloadSegmentsServerURL(String
tableNameWithType, String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
+ tableNameWithType = encode(tableNameWithType);
return String.format("%s/tables/%s/segments/needReload", endpoint,
tableNameWithType);
}
@Deprecated
private String generateValidDocIdsURL(String tableNameWithType, String
segmentName, String validDocIdsType,
String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
- segmentName = URLEncoder.encode(segmentName, StandardCharsets.UTF_8);
+ tableNameWithType = encode(tableNameWithType);
+ segmentName = encode(segmentName);
String url = String.format("%s/segments/%s/%s/validDocIds", endpoint,
tableNameWithType, segmentName);
if (validDocIdsType != null) {
url = url + "?validDocIdsType=" + validDocIdsType;
@@ -471,18 +474,15 @@ public class ServerSegmentMetadataReader {
private String generateValidDocIdsBitmapURL(String tableNameWithType, String
segmentName, String validDocIdsType,
String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
- segmentName = URLEncoder.encode(segmentName, StandardCharsets.UTF_8);
+ tableNameWithType = encode(tableNameWithType);
+ segmentName = encode(segmentName);
String url = String.format("%s/segments/%s/%s/validDocIdsBitmap",
endpoint, tableNameWithType, segmentName);
- if (validDocIdsType != null) {
- url = url + "?validDocIdsType=" + validDocIdsType;
- }
- return url;
+ return validDocIdsType != null ? url + "?validDocIdsType=" +
validDocIdsType : url;
}
private Pair<String, String> generateValidDocIdsMetadataURL(String
tableNameWithType, List<String> segmentNames,
String validDocIdsType, String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
+ tableNameWithType = encode(tableNameWithType);
TableSegments tableSegments = new TableSegments(segmentNames);
String jsonTableSegments;
try {
@@ -499,28 +499,19 @@ public class ServerSegmentMetadataReader {
}
private String generateStaleSegmentsServerURL(String tableNameWithType,
String endpoint) {
- tableNameWithType = URLEncoder.encode(tableNameWithType,
StandardCharsets.UTF_8);
+ tableNameWithType = encode(tableNameWithType);
return String.format("%s/tables/%s/segments/isStale", endpoint,
tableNameWithType);
}
- private String generateParam(String key, List<String> values) {
- String paramsStr = "";
- if (CollectionUtils.isEmpty(values)) {
- return paramsStr;
- }
- List<String> params = new ArrayList<>(values.size());
- for (String value : values) {
- params.add(key + "=" + value);
- }
- paramsStr = String.join("&", params);
- return paramsStr;
+ private static String encode(String value) {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
- public class TableReloadResponse {
- private int _numFailedResponses;
- private List<String> _serverReloadResponses;
+ public static class TableReloadResponse {
+ private final int _numFailedResponses;
+ private final List<String> _serverReloadResponses;
- TableReloadResponse(int numFailedResponses, List<String>
serverReloadResponses) {
+ private TableReloadResponse(int numFailedResponses, List<String>
serverReloadResponses) {
_numFailedResponses = numFailedResponses;
_serverReloadResponses = serverReloadResponses;
}
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java
index 57a7ad236d9..b8a6f5c2f9a 100644
---
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/HybridClusterIntegrationTest.java
@@ -19,9 +19,6 @@
package org.apache.pinot.integration.tests;
import com.fasterxml.jackson.databind.JsonNode;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -125,12 +122,7 @@ public class HybridClusterIntegrationTest extends
BaseHybridClusterIntegrationTe
segmentMetadataFromDirectEndpoint.get("segment.total.docs"));
}
// get list of segment names to pass in query params for following tests
- List<String> segmentNames = getSegmentNames(getTableName(),
TableType.OFFLINE.toString());
- List<String> segments = new ArrayList<>();
- for (String segment : segmentNames) {
- String encodedSegmentName = URLEncoder.encode(segment,
StandardCharsets.UTF_8.toString());
- segments.add(encodedSegmentName);
- }
+ List<String> segments = getSegmentNames(getTableName(),
TableType.OFFLINE.toString());
// with null column params
{
String jsonOutputStr =
sendGetRequest(_controllerRequestURLBuilder.forSegmentsMetadataFromServer(getTableName(),
@@ -143,7 +135,7 @@ public class HybridClusterIntegrationTest extends
BaseHybridClusterIntegrationTe
JsonNode segmentMetadataFromDirectEndpoint =
JsonUtils.stringToJsonNode(jsonOutputStr);
Assert.assertEquals(segmentMetadataFromAllEndpoint.get("totalDocs"),
segmentMetadataFromDirectEndpoint.get("segment.total.docs"));
-
Assert.assertEquals(tableSegmentsMetadata.get(segmentNames.get(0)).get("columns").size(),
0);
+
Assert.assertEquals(tableSegmentsMetadata.get(segments.get(0)).get("columns").size(),
0);
}
// with * column param
{
@@ -157,7 +149,7 @@ public class HybridClusterIntegrationTest extends
BaseHybridClusterIntegrationTe
JsonNode segmentMetadataFromDirectEndpoint =
JsonUtils.stringToJsonNode(jsonOutputStr);
Assert.assertEquals(segmentMetadataFromAllEndpoint.get("totalDocs"),
segmentMetadataFromDirectEndpoint.get("segment.total.docs"));
-
Assert.assertEquals(tableSegmentsMetadata.get(segmentNames.get(0)).get("columns").size(),
79);
+
Assert.assertEquals(tableSegmentsMetadata.get(segments.get(0)).get("columns").size(),
79);
}
// with specified column params
{
@@ -172,7 +164,7 @@ public class HybridClusterIntegrationTest extends
BaseHybridClusterIntegrationTe
JsonNode segmentMetadataFromDirectEndpoint =
JsonUtils.stringToJsonNode(jsonOutputStr);
Assert.assertEquals(segmentMetadataFromAllEndpoint.get("totalDocs"),
segmentMetadataFromDirectEndpoint.get("segment.total.docs"));
-
Assert.assertEquals(tableSegmentsMetadata.get(segmentNames.get(0)).get("columns").size(),
columns.size());
+
Assert.assertEquals(tableSegmentsMetadata.get(segments.get(0)).get("columns").size(),
columns.size());
}
}
diff --git
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
index 02c16814488..25c09423795 100644
---
a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
+++
b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/OfflineClusterIntegrationTest.java
@@ -3763,20 +3763,10 @@ public class OfflineClusterIntegrationTest extends
BaseClusterIntegrationTestSet
+
"/tables/mytable/metadata?columns=DivActualElapsedTime&columns=CRSElapsedTime&columns=OriginStateName"));
validateMetadataResponse(threeSVColumnsResponse, 3, 0);
- JsonNode threeSVColumnsWholeEncodedResponse =
JsonUtils.stringToJsonNode(sendGetRequest(
- getControllerBaseApiUrl() + "/tables/mytable/metadata?columns="
- +
"DivActualElapsedTime%26columns%3DCRSElapsedTime%26columns%3DOriginStateName"));
- validateMetadataResponse(threeSVColumnsWholeEncodedResponse, 3, 0);
-
JsonNode threeMVColumnsResponse =
JsonUtils.stringToJsonNode(sendGetRequest(getControllerBaseApiUrl()
+
"/tables/mytable/metadata?columns=DivLongestGTimes&columns=DivWheelsOns&columns=DivAirports"));
validateMetadataResponse(threeMVColumnsResponse, 3, 3);
- JsonNode threeMVColumnsWholeEncodedResponse =
JsonUtils.stringToJsonNode(sendGetRequest(
- getControllerBaseApiUrl() + "/tables/mytable/metadata?columns="
- +
"DivLongestGTimes%26columns%3DDivWheelsOns%26columns%3DDivAirports"));
- validateMetadataResponse(threeMVColumnsWholeEncodedResponse, 3, 3);
-
JsonNode zeroColumnResponse =
JsonUtils.stringToJsonNode(sendGetRequest(getControllerBaseApiUrl() +
"/tables/mytable/metadata"));
validateMetadataResponse(zeroColumnResponse, 0, 0);
@@ -3798,11 +3788,6 @@ public class OfflineClusterIntegrationTest extends
BaseClusterIntegrationTestSet
getControllerBaseApiUrl() + "/tables/mytable/metadata?columns="
+ "CRSElapsedTime&columns=%2A&columns=OriginStateName"));
validateMetadataResponse(starWithExtraEncodedColumnResponse, 83, 10);
-
- JsonNode starWithExtraColumnWholeEncodedResponse =
JsonUtils.stringToJsonNode(sendGetRequest(
- getControllerBaseApiUrl() + "/tables/mytable/metadata?columns="
- + "CRSElapsedTime%26columns%3D%2A%26columns%3DOriginStateName"));
- validateMetadataResponse(starWithExtraColumnWholeEncodedResponse, 83, 10);
}
private void validateMetadataResponse(JsonNode response, int numTotalColumn,
int numMVColumn) {
diff --git
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java
index 9b1c93e4dea..6386a85b52c 100644
---
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java
+++
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/ReingestionResource.java
@@ -42,6 +42,7 @@ import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import javax.inject.Inject;
+import javax.ws.rs.Encoded;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -54,6 +55,7 @@ import javax.ws.rs.core.Response;
import org.apache.pinot.common.metadata.segment.SegmentZKMetadata;
import org.apache.pinot.common.metrics.ServerMeter;
import org.apache.pinot.common.utils.LLCSegmentName;
+import org.apache.pinot.common.utils.URIUtils;
import org.apache.pinot.core.data.manager.realtime.RealtimeTableDataManager;
import
org.apache.pinot.segment.local.realtime.writer.StatelessRealtimeSegmentWriter;
import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig;
@@ -148,7 +150,8 @@ public class ReingestionResource {
@ApiResponse(code = 200, message = "Success", response =
ReingestionJob.class),
@ApiResponse(code = 500, message = "Internal server error", response =
ErrorInfo.class)
})
- public Response reingestSegment(@PathParam("segmentName") String
segmentName) {
+ public Response reingestSegment(@PathParam("segmentName") @Encoded String
encodedSegmentName) {
+ String segmentName = URIUtils.decode(encodedSegmentName);
LOGGER.info("Re-ingesting segment: {}", segmentName);
// if segment is not in LLC format, return error
diff --git
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java
index c557d0681b8..b30abe3db2f 100644
---
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java
+++
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/SegmentMetadataFetcher.java
@@ -75,13 +75,7 @@ public class SegmentMetadataFetcher {
throws JsonProcessingException {
IndexSegment segment = segmentDataManager.getSegment();
SegmentMetadata segmentMetadata = segment.getSegmentMetadata();
- Set<String> columnSet;
- if (columns.size() == 1 && columns.get(0).equals("*")) {
- // Making code consistent and returning metadata and indexes only for
non-virtual columns.
- columnSet = segment.getPhysicalColumnNames();
- } else {
- columnSet = new HashSet<>(columns);
- }
+ Set<String> columnSet = columns.contains("*") ?
segment.getPhysicalColumnNames() : new HashSet<>(columns);
ObjectNode segmentMetadataJson = (ObjectNode)
segmentMetadata.toJson(columnSet);
segmentMetadataJson.set(COLUMN_INDEX_KEY,
JsonUtils.objectToJsonNode(getIndexesForSegmentColumns(segmentDataManager,
columnSet)));
diff --git
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
index 0b4e3e97793..b482d662a5e 100644
---
a/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
+++
b/pinot-server/src/main/java/org/apache/pinot/server/api/resources/TablesResource.java
@@ -61,7 +61,6 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
-import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.helix.model.IdealState;
@@ -188,7 +187,6 @@ public class TablesResource {
}
@GET
- @Encoded
@Produces(MediaType.APPLICATION_JSON)
@Path("/tables/{tableName}/metadata")
@ApiOperation(value = "List metadata for all segments of a given table",
notes = "List segments metadata of table "
@@ -200,8 +198,8 @@ public class TablesResource {
})
public String getSegmentMetadata(
@ApiParam(value = "Table Name with type", required = true)
@PathParam("tableName") String tableName,
- @ApiParam(value = "Column name", allowMultiple = true)
@QueryParam("columns") @DefaultValue("")
- List<String> columns, @Context HttpHeaders headers)
+ @ApiParam(value = "Column name", allowMultiple = true)
@QueryParam("columns") List<String> columns,
+ @Context HttpHeaders headers)
throws WebApplicationException {
tableName = DatabaseUtils.translateTableName(tableName, headers);
InstanceDataManager instanceDataManager =
_serverInstance.getInstanceDataManager();
@@ -215,21 +213,7 @@ public class TablesResource {
throw new WebApplicationException("Table: " + tableName + " is not
found", Response.Status.NOT_FOUND);
}
- List<String> decodedColumns = new ArrayList<>(columns.size());
- for (String column : columns) {
- decodedColumns.add(URIUtils.decode(column));
- }
-
- boolean allColumns = false;
- // For robustness, loop over all columns, if any of the columns is "*",
return metadata for all columns.
- for (String column : decodedColumns) {
- if (column.equals("*")) {
- allColumns = true;
- break;
- }
- }
- Set<String> columnSet = allColumns ? null : new HashSet<>(decodedColumns);
-
+ Set<String> columnSet = columns.contains("*") ? null : new
HashSet<>(columns);
List<SegmentDataManager> segmentDataManagers =
tableDataManager.acquireAllSegments();
long totalSegmentSizeBytes = 0;
long totalNumRows = 0;
@@ -327,14 +311,13 @@ public class TablesResource {
}
@GET
- @Encoded
@Path("/tables/{tableName}/indexes")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Provide index metadata", notes = "Provide index
details for the table")
@ApiResponses(value = {
- @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500,
message = "Internal server error",
- response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table
or segment not found", response =
- ErrorInfo.class)
+ @ApiResponse(code = 200, message = "Success"),
+ @ApiResponse(code = 500, message = "Internal server error", response =
ErrorInfo.class),
+ @ApiResponse(code = 404, message = "Table or segment not found",
response = ErrorInfo.class)
})
public String getTableIndexes(
@ApiParam(value = "Table name including type", required = true, example
= "myTable_OFFLINE")
@@ -373,7 +356,6 @@ public class TablesResource {
}
@GET
- @Encoded
@Path("/tables/{tableName}/segments/{segmentName}/metadata")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Provide segment metadata", notes = "Provide segments
metadata for the segment on server")
@@ -385,16 +367,13 @@ public class TablesResource {
public String getSegmentMetadata(
@ApiParam(value = "Table name including type", required = true, example
= "myTable_OFFLINE")
@PathParam("tableName") String tableName,
- @ApiParam(value = "Segment name", required = true)
@PathParam("segmentName") String segmentName,
- @ApiParam(value = "Column name", allowMultiple = true)
@QueryParam("columns") @DefaultValue("")
- List<String> columns, @Context HttpHeaders headers) {
+ @ApiParam(value = "Segment name", required = true)
@PathParam("segmentName") @Encoded String segmentName,
+ @ApiParam(value = "Column name", allowMultiple = true)
@QueryParam("columns") List<String> columns,
+ @Context HttpHeaders headers) {
tableName = DatabaseUtils.translateTableName(tableName, headers);
- for (int i = 0; i < columns.size(); i++) {
- columns.set(i, URIUtils.decode(columns.get(i)));
- }
+ segmentName = URIUtils.decode(segmentName);
TableDataManager tableDataManager =
ServerResourceUtils.checkGetTableDataManager(_serverInstance, tableName);
- segmentName = URIUtils.decode(segmentName);
SegmentDataManager segmentDataManager =
tableDataManager.acquireSegment(segmentName);
if (segmentDataManager == null) {
throw new WebApplicationException(String.format("Table %s segments %s
does not exist", tableName, segmentName),
@@ -413,7 +392,6 @@ public class TablesResource {
}
@GET
- @Encoded
@Path("/tables/{tableName}/segments/metadata")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Provide segments metadata", notes = "Provide segments
metadata for the segments on server")
@@ -425,36 +403,25 @@ public class TablesResource {
public String getSegmentsMetadata(
@ApiParam(value = "Table name including type", required = true, example
= "myTable_OFFLINE")
@PathParam("tableName") String tableName,
- @Nullable @ApiParam(value = "Segments name", allowMultiple = true)
@QueryParam("segments") List<String> segments,
- @Nullable @ApiParam(value = "Column name", allowMultiple = true)
@QueryParam("columns") List<String> columns,
+ @ApiParam(value = "Segments to include (all if not specified)",
allowMultiple = true) @QueryParam("segments")
+ List<String> segments,
+ @ApiParam(value = "Columns to include (use '*' to include all)",
allowMultiple = true) @QueryParam("columns")
+ List<String> columns,
@Context HttpHeaders headers) {
tableName = DatabaseUtils.translateTableName(tableName, headers);
TableDataManager tableDataManager =
ServerResourceUtils.checkGetTableDataManager(_serverInstance, tableName);
- // decode columns and segments
- List<String> decodedSegments = new ArrayList<>();
- if (CollectionUtils.isNotEmpty(segments)) {
- for (String segment : segments) {
- decodedSegments.add(URIUtils.decode(segment));
- }
- }
List<SegmentDataManager> segmentDataManagers;
- if (!decodedSegments.isEmpty()) {
- segmentDataManagers = tableDataManager.acquireSegments(decodedSegments,
new ArrayList<>());
+ if (!segments.isEmpty()) {
+ segmentDataManagers = tableDataManager.acquireSegments(segments, new
ArrayList<>());
} else {
segmentDataManagers = tableDataManager.acquireAllSegments();
}
- List<String> decodedColumns = new ArrayList<>();
- if (CollectionUtils.isNotEmpty(columns)) {
- for (String column: columns) {
- decodedColumns.add(URIUtils.decode(column));
- }
- }
// get metadata for every segment in the list
Map<String, JsonNode> response = new HashMap<>();
try {
for (SegmentDataManager segmentDataManager: segmentDataManagers) {
String segmentName = segmentDataManager.getSegmentName();
- String segmentMetadata =
SegmentMetadataFetcher.getSegmentMetadata(segmentDataManager, decodedColumns);
+ String segmentMetadata =
SegmentMetadataFetcher.getSegmentMetadata(segmentDataManager, columns);
JsonNode segmentMetadataJson =
JsonUtils.stringToJsonNode(segmentMetadata);
response.put(segmentName, segmentMetadataJson);
}
@@ -514,6 +481,7 @@ public class TablesResource {
@Context HttpHeaders httpHeaders)
throws Exception {
tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType,
httpHeaders);
+ segmentName = URIUtils.decode(segmentName);
LOGGER.info("Received a request to download segment {} for table {}",
segmentName, tableNameWithType);
// Validate data access
ServerResourceUtils.validateDataAccess(_accessControlFactory,
tableNameWithType, httpHeaders);
@@ -628,8 +596,8 @@ public class TablesResource {
@ApiParam(value = "Name of the table with type REALTIME", required =
true, example = "myTable_REALTIME")
@PathParam("tableNameWithType") String tableNameWithType,
@ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") @Encoded String segmentName,
- @ApiParam(value = "Valid doc ids type")
- @QueryParam("validDocIdsType") String validDocIdsType, @Context
HttpHeaders httpHeaders) {
+ @ApiParam(value = "Valid doc ids type") @QueryParam("validDocIdsType")
@Nullable String validDocIdsType,
+ @Context HttpHeaders httpHeaders) {
tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType,
httpHeaders);
segmentName = URIUtils.decode(segmentName);
LOGGER.info("Received a request to download validDocIds for segment {}
table {}", segmentName, tableNameWithType);
@@ -680,18 +648,16 @@ public class TablesResource {
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Provides segment validDocId metadata", notes =
"Provides segment validDocId metadata")
@ApiResponses(value = {
- @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500,
message = "Internal server error",
- response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table
or segment not found", response =
- ErrorInfo.class)
+ @ApiResponse(code = 200, message = "Success"),
+ @ApiResponse(code = 500, message = "Internal server error", response =
ErrorInfo.class),
+ @ApiResponse(code = 404, message = "Table or segment not found",
response = ErrorInfo.class)
})
public String getValidDocIdsMetadata(
@ApiParam(value = "Table name including type", required = true, example
= "myTable_REALTIME")
@PathParam("tableNameWithType") String tableNameWithType,
- @ApiParam(value = "Valid doc ids type")
- @QueryParam("validDocIdsType") String validDocIdsType,
+ @ApiParam(value = "Valid doc ids type") @QueryParam("validDocIdsType")
String validDocIdsType,
@ApiParam(value = "Segment name", allowMultiple = true)
@QueryParam("segmentNames") List<String> segmentNames,
- @Context HttpHeaders headers)
- throws Exception {
+ @Context HttpHeaders headers) {
tableNameWithType = DatabaseUtils.translateTableName(tableNameWithType,
headers);
return ResourceUtils.convertToJsonString(
processValidDocIdsMetadata(tableNameWithType, segmentNames,
validDocIdsType));
@@ -702,9 +668,9 @@ public class TablesResource {
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Provides segment validDocIds metadata", notes =
"Provides segment validDocIds metadata")
@ApiResponses(value = {
- @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500,
message = "Internal server error",
- response = ErrorInfo.class), @ApiResponse(code = 404, message = "Table
or segment not found", response =
- ErrorInfo.class)
+ @ApiResponse(code = 200, message = "Success"),
+ @ApiResponse(code = 500, message = "Internal server error", response =
ErrorInfo.class),
+ @ApiResponse(code = 404, message = "Table or segment not found",
response = ErrorInfo.class)
})
public String getValidDocIdsMetadata(
@ApiParam(value = "Table name including type", required = true, example
= "myTable_REALTIME")
@@ -869,11 +835,12 @@ public class TablesResource {
public String uploadLLCSegment(
@ApiParam(value = "Name of the REALTIME table", required = true)
@PathParam("realtimeTableName")
String realtimeTableName,
- @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") String segmentName,
+ @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") @Encoded String segmentName,
@QueryParam("uploadTimeoutMs") @DefaultValue("-1") int timeoutMs,
@Context HttpHeaders headers)
throws Exception {
realtimeTableName = DatabaseUtils.translateTableName(realtimeTableName,
headers);
+ segmentName = URIUtils.decode(segmentName);
LOGGER.info("Received a request to upload low level consumer segment {}
for table {}", segmentName,
realtimeTableName);
@@ -943,11 +910,12 @@ public class TablesResource {
public TableLLCSegmentUploadResponse uploadLLCSegmentV2(
@ApiParam(value = "Name of the REALTIME table", required = true)
@PathParam("realtimeTableNameWithType")
String realtimeTableNameWithType,
- @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") String segmentName,
+ @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") @Encoded String segmentName,
@QueryParam("uploadTimeoutMs") @DefaultValue("-1") int timeoutMs,
@Context HttpHeaders headers)
throws Exception {
realtimeTableNameWithType =
DatabaseUtils.translateTableName(realtimeTableNameWithType, headers);
+ segmentName = URIUtils.decode(segmentName);
LOGGER.info("Received a request to upload low level consumer segment {}
for table {}", segmentName,
realtimeTableNameWithType);
@@ -1014,10 +982,11 @@ public class TablesResource {
public String uploadCommittedSegment(
@ApiParam(value = "Name of the real-time table", required = true)
@PathParam("realtimeTableName")
String realtimeTableName,
- @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") String segmentName,
+ @ApiParam(value = "Name of the segment", required = true)
@PathParam("segmentName") @Encoded String segmentName,
@QueryParam("uploadTimeoutMs") @DefaultValue("-1") int timeoutMs,
@Context HttpHeaders headers)
throws Exception {
realtimeTableName = DatabaseUtils.translateTableName(realtimeTableName,
headers);
+ segmentName = URIUtils.decode(segmentName);
LOGGER.info("Received a request to upload committed segment: {} for table:
{}", segmentName, realtimeTableName);
// Check it's real-time table
diff --git
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
index c17bc7c0b61..2da3d879d0a 100644
---
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
+++
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/ControllerRequestURLBuilder.java
@@ -20,12 +20,10 @@ package org.apache.pinot.spi.utils.builder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
-import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.pinot.spi.config.table.TableType;
import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType;
@@ -342,46 +340,11 @@ public class ControllerRequestURLBuilder {
}
public String forTableAggregateMetadata(String tableName, @Nullable
List<String> columns) {
- return StringUtil.join("/", _baseUrl, "tables", tableName, "metadata") +
constructColumnsParameter(columns);
- }
-
- private String constructColumnsParameter(@Nullable List<String> columns) {
- if (!CollectionUtils.isEmpty(columns)) {
- StringBuilder parameter = new StringBuilder();
- parameter.append("?columns=");
- parameter.append(columns.get(0));
- int numColumns = columns.size();
- if (numColumns > 1) {
- for (int i = 1; i < numColumns; i++) {
- parameter.append("&columns=").append(columns.get(i));
- }
- }
- return parameter.toString();
- } else {
- return "";
- }
- }
-
- private String constructQueryParametersString(Map<String, List<String>>
queryParams) {
- if (queryParams.isEmpty()) {
- return "";
- }
- StringBuilder query = new StringBuilder("?");
- boolean firstParam = true;
- for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
- String key = entry.getKey();
- for (String value : entry.getValue()) {
- if (!firstParam) {
- query.append("&");
- }
- query.append(key).append("=").append(value);
- firstParam = false;
- }
- }
- return query.toString();
+ String columnsParam = UrlBuilderUtils.generateColumnsParam(columns);
+ String url = StringUtil.join("/", _baseUrl, "tables", tableName,
"metadata");
+ return columnsParam != null ? url + "?" + columnsParam : url;
}
-
public String forSchemaValidate() {
return StringUtil.join("/", _baseUrl, "schemas", "validate");
}
@@ -471,16 +434,10 @@ public class ControllerRequestURLBuilder {
public String forSegmentsMetadataFromServer(String tableName, @Nullable
List<String> columns,
@Nullable List<String> segments) {
- String basePath = StringUtil.join("/", _baseUrl, "segments", tableName,
"metadata");
- Map<String, List<String>> queryParams = new LinkedHashMap<>();
- if (!CollectionUtils.isEmpty(columns)) {
- queryParams.put("columns", columns);
- }
- if (!CollectionUtils.isEmpty(segments)) {
- queryParams.put("segments", segments);
- }
- String queryString = constructQueryParametersString(queryParams);
- return basePath + queryString;
+ tableName = encode(tableName);
+ String columnsAndSegmentsParam =
UrlBuilderUtils.generateColumnsAndSegmentsParam(columns, segments);
+ String url = StringUtil.join("/", _baseUrl, "segments", tableName,
"metadata");
+ return columnsAndSegmentsParam != null ? url + "?" +
columnsAndSegmentsParam : url;
}
public String forSegmentMetadata(String tableName, String segmentName) {
diff --git
a/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UrlBuilderUtils.java
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UrlBuilderUtils.java
new file mode 100644
index 00000000000..d67e7887082
--- /dev/null
+++
b/pinot-spi/src/main/java/org/apache/pinot/spi/utils/builder/UrlBuilderUtils.java
@@ -0,0 +1,75 @@
+/**
+ * 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.pinot.spi.utils.builder;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.collections4.CollectionUtils;
+
+
+public class UrlBuilderUtils {
+ private UrlBuilderUtils() {
+ }
+
+ @Nullable
+ public static String generateColumnsParam(@Nullable List<String> columns) {
+ if (CollectionUtils.isEmpty(columns)) {
+ return null;
+ }
+ StringBuilder builder = new
StringBuilder("columns=").append(encode(columns.get(0)));
+ int numColumns = columns.size();
+ for (int i = 1; i < numColumns; i++) {
+ builder.append("&columns=").append(encode(columns.get(i)));
+ }
+ return builder.toString();
+ }
+
+ @Nullable
+ public static String generateSegmentsParam(@Nullable List<String> segments) {
+ if (CollectionUtils.isEmpty(segments)) {
+ return null;
+ }
+ StringBuilder builder = new
StringBuilder("segments=").append(encode(segments.get(0)));
+ int numSegments = segments.size();
+ for (int i = 1; i < numSegments; i++) {
+ builder.append("&segments=").append(encode(segments.get(i)));
+ }
+ return builder.toString();
+ }
+
+ public static String encode(String value) {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8);
+ }
+
+ @Nullable
+ public static String generateColumnsAndSegmentsParam(@Nullable List<String>
columns,
+ @Nullable List<String> segments) {
+ String columnsParam = generateColumnsParam(columns);
+ String segmentsParam = generateSegmentsParam(segments);
+ if (columnsParam == null) {
+ return segmentsParam;
+ }
+ if (segmentsParam == null) {
+ return columnsParam;
+ }
+ return columnsParam + "&" + segmentsParam;
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]