Search in sources :

Example 1 with PathSegmentSyntaxException

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));
    }
}
Also used : PathSegmentSyntaxException(com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException) DataMap(com.linkedin.data.DataMap)

Example 2 with PathSegmentSyntaxException

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&param1=value1&param2=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))&param1=value1&param2=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);
}
Also used : URI(java.net.URI) PathSpec(com.linkedin.data.schema.PathSpec) DataMap(com.linkedin.data.DataMap) QueryParamsDataMap(com.linkedin.restli.internal.common.QueryParamsDataMap) DataList(com.linkedin.data.DataList) PathSegmentSyntaxException(com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException) ComplexResourceKey(com.linkedin.restli.common.ComplexResourceKey) MultivaluedMap(com.linkedin.jersey.core.util.MultivaluedMap) TestRecord(com.linkedin.restli.client.test.TestRecord) Test(org.testng.annotations.Test)

Example 3 with PathSegmentSyntaxException

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);
}
Also used : MapMap(com.linkedin.restli.internal.common.PathSegment.MapMap) DataList(com.linkedin.data.DataList) PathSegmentSyntaxException(com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException) ArrayList(java.util.ArrayList) DataList(com.linkedin.data.DataList) List(java.util.List) ByteString(com.linkedin.data.ByteString) HashMap(java.util.HashMap) ListMap(com.linkedin.restli.internal.common.PathSegment.ListMap) DataMap(com.linkedin.data.DataMap) Map(java.util.Map) MapMap(com.linkedin.restli.internal.common.PathSegment.MapMap) DataMap(com.linkedin.data.DataMap)

Example 4 with PathSegmentSyntaxException

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;
}
Also used : RoutingException(com.linkedin.restli.server.RoutingException) PathSegmentSyntaxException(com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException) CompoundKey(com.linkedin.restli.common.CompoundKey) TemplateRuntimeException(com.linkedin.data.template.TemplateRuntimeException)

Example 5 with PathSegmentSyntaxException

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);
}
Also used : RoutingException(com.linkedin.restli.server.RoutingException) CompoundKey(com.linkedin.restli.common.CompoundKey) ProtocolVersion(com.linkedin.restli.common.ProtocolVersion) DataMap(com.linkedin.data.DataMap) DataList(com.linkedin.data.DataList) PathSegmentSyntaxException(com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException) RestLiServiceException(com.linkedin.restli.server.RestLiServiceException) ComplexResourceKey(com.linkedin.restli.common.ComplexResourceKey) TemplateRuntimeException(com.linkedin.data.template.TemplateRuntimeException) LinkedList(java.util.LinkedList) DataList(com.linkedin.data.DataList) List(java.util.List) ComplexResourceKey(com.linkedin.restli.common.ComplexResourceKey) Key(com.linkedin.restli.server.Key) CompoundKey(com.linkedin.restli.common.CompoundKey) HashSet(java.util.HashSet)

Aggregations

PathSegmentSyntaxException (com.linkedin.restli.internal.common.PathSegment.PathSegmentSyntaxException)6 DataMap (com.linkedin.data.DataMap)4 DataList (com.linkedin.data.DataList)3 RoutingException (com.linkedin.restli.server.RoutingException)3 TemplateRuntimeException (com.linkedin.data.template.TemplateRuntimeException)2 ComplexResourceKey (com.linkedin.restli.common.ComplexResourceKey)2 CompoundKey (com.linkedin.restli.common.CompoundKey)2 List (java.util.List)2 ByteString (com.linkedin.data.ByteString)1 PathSpec (com.linkedin.data.schema.PathSpec)1 RecordTemplate (com.linkedin.data.template.RecordTemplate)1 MultivaluedMap (com.linkedin.jersey.core.util.MultivaluedMap)1 TestRecord (com.linkedin.restli.client.test.TestRecord)1 ProtocolVersion (com.linkedin.restli.common.ProtocolVersion)1 ListMap (com.linkedin.restli.internal.common.PathSegment.ListMap)1 MapMap (com.linkedin.restli.internal.common.PathSegment.MapMap)1 QueryParamsDataMap (com.linkedin.restli.internal.common.QueryParamsDataMap)1 Key (com.linkedin.restli.server.Key)1 RestLiServiceException (com.linkedin.restli.server.RestLiServiceException)1 URI (java.net.URI)1