use of com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException in project rest.li by linkedin.
the class ArgumentUtils method parseCompoundKeyV2.
/**
*
* @param urlString {@link String} representation of the v2 compound key
* @param keys the {@link Key}s representing each part of the compound key.
* @return a {@link CompoundKey}
* @throws IllegalArgumentException if there are unexpected key parts in the urlString that are not in keys.
* @throws PathSegmentSyntaxException if the given string is not a valid encoded v2 compound key
*/
private static CompoundKey parseCompoundKeyV2(final String urlString, final Collection<Key> keys) throws PathSegmentSyntaxException, IllegalArgumentException {
DataMap dataMap;
// dataMap looks like keyName1: keyValue1, keyName2: keyValue2, ...
Object parsedObject = URIElementParser.parse(urlString);
if (parsedObject instanceof DataMap) {
dataMap = (DataMap) parsedObject;
return dataMapToCompoundKey(dataMap, keys);
} else {
throw new PathSegmentSyntaxException(String.format("input '%s' is not a valid CompoundKey", urlString));
}
}
use of com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException in project rest.li by linkedin.
the class BatchGetRequestBuilderTest method testComplexKeyBatching.
@SuppressWarnings("unchecked")
@Test
public void testComplexKeyBatching() throws URISyntaxException, PathSegmentSyntaxException {
String expectedProtocol1Uri = "/?fields=id,message&ids%5B0%5D.$params.id=1&ids%5B0%5D.$params.message=paramMessage1&ids%5B0%5D.id=1&ids%5B0%5D.message=keyMessage1&ids%5B1%5D.$params.id=2&ids%5B1%5D.$params.message=paramMessage2&ids%5B1%5D.id=2&ids%5B1%5D.message=keyMessage2&ids%5B2%5D.$params.id=3&ids%5B2%5D.$params.message=paramMessage3&ids%5B2%5D.id=3&ids%5B2%5D.message=keyMessage3¶m1=value1¶m2=value2";
ComplexResourceKey<TestRecord, TestRecord> complexKey1 = buildComplexKey(1L, "keyMessage1", 1L, "paramMessage1");
ComplexResourceKey<TestRecord, TestRecord> complexKey2 = buildComplexKey(2L, "keyMessage2", 2L, "paramMessage2");
ComplexResourceKey<TestRecord, TestRecord> complexKey3 = buildComplexKey(3L, "keyMessage3", 3L, "paramMessage3");
BatchGetRequestBuilder<ComplexResourceKey<TestRecord, TestRecord>, TestRecord> batchRequestBuilder1 = new BatchGetRequestBuilder<ComplexResourceKey<TestRecord, TestRecord>, TestRecord>("/", TestRecord.class, _complexResourceSpec, RestliRequestOptions.DEFAULT_OPTIONS);
@SuppressWarnings("unchecked") ComplexResourceKey<TestRecord, TestRecord>[] complexKeys1 = (ComplexResourceKey<TestRecord, TestRecord>[]) Arrays.asList(complexKey1, complexKey2).toArray();
batchRequestBuilder1.ids(complexKeys1).fields(FIELDS.id()).setParam("param2", "value2").setParam("param1", "value1");
BatchGetRequestBuilder<ComplexResourceKey<TestRecord, TestRecord>, TestRecord> batchRequestBuilder2 = new BatchGetRequestBuilder<ComplexResourceKey<TestRecord, TestRecord>, TestRecord>("/", TestRecord.class, _complexResourceSpec, RestliRequestOptions.DEFAULT_OPTIONS);
@SuppressWarnings("unchecked") ComplexResourceKey<TestRecord, TestRecord>[] complexKeys2 = (ComplexResourceKey<TestRecord, TestRecord>[]) Arrays.asList(complexKey2, complexKey3).toArray();
batchRequestBuilder2.ids(complexKeys2).fields(FIELDS.id(), FIELDS.message()).setParam("param1", "value1").setParam("param2", "value2");
BatchGetKVRequest<ComplexResourceKey<TestRecord, TestRecord>, TestRecord> batchRequest1 = batchRequestBuilder1.buildKV();
BatchGetKVRequest<ComplexResourceKey<TestRecord, TestRecord>, TestRecord> batchRequest2 = batchRequestBuilder2.buildKV();
@SuppressWarnings("unchecked") BatchGetKVRequest<ComplexResourceKey<TestRecord, TestRecord>, TestRecord> batchingRequest = BatchGetRequestBuilder.batchKV(Arrays.asList(batchRequest1, batchRequest2));
URI actualProtocol1Uri = RestliUriBuilderUtil.createUriBuilder(batchingRequest, AllProtocolVersions.RESTLI_PROTOCOL_1_0_0.getProtocolVersion()).build();
MultivaluedMap actualParams = UriComponent.decodeQuery(actualProtocol1Uri, true);
MultivaluedMap expectedUriParams = UriComponent.decodeQuery(URI.create(expectedProtocol1Uri), true);
DataMap expectedParamsDataMap = null;
DataMap actualParamsDataMap = null;
try {
expectedParamsDataMap = QueryParamsDataMap.parseDataMapKeys(expectedUriParams);
actualParamsDataMap = QueryParamsDataMap.parseDataMapKeys(actualParams);
} catch (PathSegmentSyntaxException e) {
// Should never happen
Assert.fail("Failed to parse data map keys!");
}
Assert.assertEquals(actualProtocol1Uri.getPath(), "/");
// Apparently due to using set to compact the list of ids in
// BatchGetRequestBuilder.batch() the order of the parameters on the url is no longer
// reliable.
DataList actualIds = (DataList) actualParamsDataMap.remove(RestConstants.QUERY_BATCH_IDS_PARAM);
DataList expectedIds = (DataList) expectedParamsDataMap.remove(RestConstants.QUERY_BATCH_IDS_PARAM);
Assert.assertEquals(new HashSet<Object>(actualIds), new HashSet<Object>(expectedIds));
Assert.assertEquals(actualParamsDataMap, expectedParamsDataMap);
Assert.assertEquals(batchingRequest.getBaseUriTemplate(), batchRequest1.getBaseUriTemplate());
Assert.assertEquals(batchingRequest.getPathKeys(), batchRequest1.getPathKeys());
Assert.assertEquals(batchingRequest.getFields(), new HashSet<PathSpec>(Arrays.asList(FIELDS.id(), FIELDS.message())));
Assert.assertEquals(batchingRequest.getObjectIds(), new HashSet<Object>(Arrays.asList(complexKey1, complexKey2, complexKey3)));
String expectedProtocol2Uri = "/?fields=id,message&ids=List(($params:(id:1,message:paramMessage1),id:1,message:keyMessage1),($params:(id:2,message:paramMessage2),id:2,message:keyMessage2),($params:(id:3,message:paramMessage3),id:3,message:keyMessage3))¶m1=value1¶m2=value2";
URI actualProtocol2Uri = RestliUriBuilderUtil.createUriBuilder(batchingRequest, AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion()).build();
Assert.assertEquals(actualProtocol2Uri.getPath(), "/");
actualParams = UriComponent.decodeQuery(actualProtocol2Uri, true);
MultivaluedMap expectedParams = UriComponent.decodeQuery(URI.create(expectedProtocol2Uri), true);
// we can't compare the query param "ids" directly as ID ordering is not preserved while batching in
// BatchGetRequestBuilder.batch()
Assert.assertEquals(actualParams.get("ids").size(), 1);
Assert.assertEquals(actualParams.get("ids").size(), expectedParams.get("ids").size());
// parse out the "ids" param into a DataList and then convert it into a set
String actualProtocol2IdsAsString = actualParams.remove("ids").get(0);
String expectedProtocol2IdsAsString = expectedParams.remove("ids").get(0);
DataList actualProtocol2Ids = (DataList) URIElementParser.parse(actualProtocol2IdsAsString);
DataList expectedProtocol2Ids = (DataList) URIElementParser.parse(expectedProtocol2IdsAsString);
Assert.assertEquals(new HashSet<Object>(actualProtocol2Ids.values()), new HashSet<Object>(expectedProtocol2Ids.values()));
// apart from the "ids" fields everything else should be the same
Assert.assertEquals(actualParams, expectedParams);
}
use of com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException in project rest.li by linkedin.
the class QueryParamsDataMap method parseDataMapKeys.
/**
* Parse a multi-map representing query parameters into a DataMap, as follows.
*
* Multi-indexed parameter names, such as ids[0][2] or ids[0,2] are not
* currently supported.
*
* For example, the following query string:
*
* /groupMemberships?ids[0].params.versionTag=tag1&ids[0].params.authToken=tok1&ids[0].memberID=1&ids[0].groupID=2& \
* ids[1].params.versionTag=tag2&ids[1].params.authToken=tok2&ids[1].memberID=2&ids[1].groupID=2& \
* q=someFinder
*
* is parsed into the following data map:
*
* {"ids" : [
* {
* "memberID" : "1",
* "groupID" : "2",
* "params" : {
* "authToken" : "tok1",
* "versionTag" : "tag1"
* }
* },
* { "memberID" : "2",
* "groupID" : "2",
* "params" : {
* "authToken" : "tok2",
* "versionTag" : "tag2"
* }
* }
* ],
* "q" : "someFinder"
* }
*
*
* Note: at this point the data map is not typed - all names and values are
* parsed as strings.
*
* Note: when parsing indexed parameter names, those will be converted to a list,
* preserving the order of the values according to the index, but ignoring any
* possible "holes" in the index sequence. The index values therefore only
* serve to define order of the parameter values, rather than their actual
* position in any collection.
*
* @param queryParameters the query parameters
* @return - the DataMap represented by potentially hierarchical keys encoded
* by the multi-part parameter names.
*
* @throws PathSegmentSyntaxException
*/
public static DataMap parseDataMapKeys(Map<String, List<String>> queryParameters) throws PathSegmentSyntaxException {
// The parameters are parsed into an intermediary structure comprised of
// HashMap<String,Object> and HashMap<Integer,Object>, defined respectively
// as MapMap and ListMap for convenience. This is done for two reasons:
// - first, indexed keys representing lists are parsed into ListMaps keyed on
// index values, since the indices may come in any order in the query parameter,
// while we want to preserve the order.
// - second, DataMap only accepts Data objects as values, so ListMaps cannot
// be stored there, so using an intermediary structure even for maps.
MapMap dataMap = new MapMap();
for (Map.Entry<String, List<String>> entry : queryParameters.entrySet()) {
// As per the notation above, we no longer support multiple occurrences of
// a parameter (considering its full multi-part and indexed name), i.e
// there should be only a single entry in each list. For backward compatibility
// as well as ease of use, repeated parameters are still allowed if they
// are "simple", i.e. they are not multi-part or indexed.
List<String> valueList = entry.getValue();
if (valueList.size() == 1) {
String[] key = SEGMENT_DELIMITER_PATTERN.split(entry.getKey());
parseParameter(key, valueList.get(0), dataMap);
} else {
String parameterName = entry.getKey();
// indexed and then simulate the index for each one.
if (parameterName.indexOf('.') != -1)
throw new PathSegmentSyntaxException("Multiple values of complex query parameter are not supported");
if (parameterName.charAt(parameterName.length() - 1) == ']')
throw new PathSegmentSyntaxException("Multiple values of indexed query parameter are not supported");
if (dataMap.containsKey(parameterName))
throw new PathSegmentSyntaxException("Conflicting references to key " + parameterName + "[0]");
else {
dataMap.put(parameterName, new DataList(valueList));
}
}
}
return (DataMap) convertToDataCollection(dataMap);
}
use of com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException in project rest.li by linkedin.
the class RestLiRouter method parseCompoundKey.
private static CompoundKey parseCompoundKey(final ResourceModel resource, final ServerResourceContext context, final String pathSegment) {
CompoundKey compoundKey;
try {
compoundKey = ArgumentUtils.parseCompoundKey(pathSegment, resource.getKeys(), context.getRestliProtocolVersion());
} catch (PathSegmentSyntaxException e) {
throw new RoutingException(String.format("input %s is not a Compound key", pathSegment), HttpStatus.S_400_BAD_REQUEST.getCode(), e);
} catch (IllegalArgumentException e) {
throw new RoutingException(String.format("input %s is not a Compound key", pathSegment), HttpStatus.S_400_BAD_REQUEST.getCode(), e);
} catch (TemplateRuntimeException e) {
// thrown from DateTemplateUtil.coerceOutput
throw new RoutingException(String.format("Compound key parameter value %s is invalid", pathSegment), HttpStatus.S_400_BAD_REQUEST.getCode(), e);
}
for (String simpleKeyName : compoundKey.getPartKeys()) {
context.getPathKeys().append(simpleKeyName, compoundKey.getPart(simpleKeyName));
}
context.getPathKeys().append(resource.getKeyName(), compoundKey);
return compoundKey;
}
use of com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException in project rest.li by linkedin.
the class RestLiRouter method parseBatchKeysParameter.
private void parseBatchKeysParameter(final ResourceModel resource, final ServerResourceContext context) {
Class<?> keyClass = resource.getKeyClass();
ProtocolVersion version = context.getRestliProtocolVersion();
final Set<Object> batchKeys;
try {
if (context.getParameters().containsKey(RestConstants.ALT_KEY_PARAM)) {
batchKeys = parseAlternativeBatchKeys(resource, context);
} else if (ComplexResourceKey.class.equals(keyClass)) {
// Parse all query parameters into a data map.
DataMap allParametersDataMap = context.getParameters();
// Get the batch request keys from the IDS list at the root of the map.
DataList batchIds = allParametersDataMap.getDataList(RestConstants.QUERY_BATCH_IDS_PARAM);
if (batchIds == null) {
batchKeys = null;
} else if (batchIds.isEmpty()) {
batchKeys = Collections.emptySet();
} else {
batchKeys = new HashSet<Object>();
// Validate the complex keys and put them into the context batch keys
for (Object complexKey : batchIds) {
if (!(complexKey instanceof DataMap)) {
log.warn("Invalid structure of key '" + complexKey.toString() + "', skipping key.");
context.getBatchKeyErrors().put(complexKey, new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST));
continue;
}
batchKeys.add(ComplexResourceKey.buildFromDataMap((DataMap) complexKey, ComplexKeySpec.forClassesMaybeNull(resource.getKeyKeyClass(), resource.getKeyParamsClass())));
}
}
} else if (CompoundKey.class.equals(keyClass) && version.compareTo(AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion()) >= 0) {
DataMap allParametersDataMap = context.getParameters();
// Get the batch request keys from the IDS list at the root of the map.
DataList batchIds = allParametersDataMap.getDataList(RestConstants.QUERY_BATCH_IDS_PARAM);
if (batchIds == null) {
batchKeys = null;
} else if (batchIds.isEmpty()) {
batchKeys = Collections.emptySet();
} else {
batchKeys = new HashSet<Object>();
// Validate the compound keys and put them into the contex batch keys
for (Object compoundKey : batchIds) {
if (!(compoundKey instanceof DataMap)) {
log.warn("Invalid structure of key '" + compoundKey.toString() + "', skipping key.");
context.getBatchKeyErrors().put(compoundKey.toString(), new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST));
continue;
}
CompoundKey finalKey;
try {
finalKey = ArgumentUtils.dataMapToCompoundKey((DataMap) compoundKey, resource.getKeys());
} catch (IllegalArgumentException e) {
log.warn("Invalid structure of key '" + compoundKey.toString() + "', skipping key.");
context.getBatchKeyErrors().put(compoundKey.toString(), new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST));
continue;
}
batchKeys.add(finalKey);
}
}
} else // collection batch get in v2, collection or association batch get in v1
if (context.hasParameter(RestConstants.QUERY_BATCH_IDS_PARAM)) {
batchKeys = new HashSet<Object>();
List<String> ids = context.getParameterValues(RestConstants.QUERY_BATCH_IDS_PARAM);
if (version.compareTo(AllProtocolVersions.RESTLI_PROTOCOL_2_0_0.getProtocolVersion()) >= 0) {
for (String id : ids) {
Key key = resource.getPrimaryKey();
Object value;
try {
// in v2, compound keys have already been converted and dealt with, so all we need to do here is convert simple values.
value = ArgumentUtils.convertSimpleValue(id, key.getDataSchema(), key.getType());
batchKeys.add(value);
} catch (NumberFormatException e) {
throw new RoutingException("NumberFormatException parsing batch key '" + id + "'", HttpStatus.S_400_BAD_REQUEST.getCode(), e);
} catch (IllegalArgumentException e) {
throw new RoutingException("IllegalArgumentException parsing batch key '" + id + "'", HttpStatus.S_400_BAD_REQUEST.getCode(), e);
}
}
} else {
for (String id : ids) {
try {
// in v1, compound keys have not been fully parsed or dealt with yet, so we need to take them into account.
Object value = parseKeyFromBatchV1(id, resource);
batchKeys.add(value);
} catch (NumberFormatException e) {
log.warn("Caught NumberFormatException parsing batch key '" + id + "', skipping key.");
context.getBatchKeyErrors().put(id, new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, null, e));
} catch (IllegalArgumentException e) {
log.warn("Caught IllegalArgumentException parsing batch key '" + id + "', skipping key.");
context.getBatchKeyErrors().put(id, new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, null, e));
} catch (PathSegmentSyntaxException e) {
log.warn("Caught IllegalArgumentException parsing batch key '" + id + "', skipping key.");
context.getBatchKeyErrors().put(id, new RestLiServiceException(HttpStatus.S_400_BAD_REQUEST, null, e));
}
}
}
} else {
batchKeys = null;
}
} catch (TemplateRuntimeException e) {
// thrown from DateTemplateUtil.coerceOutput
throw new RoutingException("Batch key parameter value is invalid", HttpStatus.S_400_BAD_REQUEST.getCode(), e);
}
context.getPathKeys().setBatchKeys(batchKeys);
}
Aggregations