Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import java.time.temporal.ChronoField;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;

import javax.annotation.Nonnull;

Expand Down Expand Up @@ -52,6 +54,20 @@ public interface ODataProtocol extends ODataResponseDescriptor, ODataLiteralSeri
@Nonnull
Map.Entry<String, String> getQueryOptionInlineCount( boolean optionEnabled );

/**
* Check whether custom query parameter can be attached to a structured-query.
*
* @param isRoot
* indicates whether the structured-query is the root query or a nested query.
* @param key
* the key of the custom query parameter.
* @return true if the custom query parameter is allowed, false otherwise.
*/
default boolean allowCustomQueryParameter( final boolean isRoot, @Nonnull final String key )
{
return isRoot;
}

/**
* OData protocol v2.
*/
Expand Down Expand Up @@ -190,6 +206,14 @@ public String toString()
{
return "OData " + protocolVersion;
}

@Override
public boolean allowCustomQueryParameter( final boolean isRoot, @Nonnull final String key )
{
final Predicate<String> expandOption =
Set.of("$count", "$filter", "$orderby", "$search", "$skip", "$top")::contains;
return isRoot || expandOption.test(key);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ static String serializeAndEncodeQuery( @Nonnull final StructuredQuery query, fin
.map(q -> String.format(parameterString, q))
.forEach(parameters::add));

if( query.isRoot() ) {
query
.getCustomParameters()
.forEach(( key, value ) -> parameters.add(key + "=" + conditionalEncode(value, applyEncoding)));
for( final Map.Entry<String, String> customParam : query.getCustomParameters().entrySet() ) {
final String key = customParam.getKey();
if( query.getProtocol().allowCustomQueryParameter(query.isRoot(), key) ) {
parameters.add(key + "=" + conditionalEncode(customParam.getValue(), applyEncoding));
}
}

final String queryElementSeparator = query.isRoot() ? SEPARATOR_ROOT_QUERY : SEPARATOR_SUB_QUERY;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol.V2;
import static com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol.V4;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
Expand All @@ -23,7 +25,6 @@
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientAccessor;
import com.sap.cloud.sdk.datamodel.odata.client.ODataProtocol;
import com.sap.cloud.sdk.datamodel.odata.client.expression.FieldReference;
import com.sap.cloud.sdk.datamodel.odata.client.expression.ODataResourcePath;
import com.sap.cloud.sdk.datamodel.odata.client.expression.OrderExpression;
Expand Down Expand Up @@ -63,7 +64,7 @@ void testQueryParameters()
final String queryString = "$select=select1&$expand=expand1,expand2($select=nestedSelect;$top=10)&$top=1";

final ODataRequestRead request =
new ODataRequestRead(ODATA_SERVICE_PATH, ODATA_ENTITY_COLLECTION, queryString, ODataProtocol.V4);
new ODataRequestRead(ODATA_SERVICE_PATH, ODATA_ENTITY_COLLECTION, queryString, V4);
request.addQueryParameter("query-param1", "qp1");
request.addQueryParameter("query-param2", "qp2");
request.addHeader("header-key1", "hk1");
Expand Down Expand Up @@ -91,22 +92,37 @@ void testQueryParameters()
@Test
void testV4QueryExpand()
{
final StructuredQuery query = StructuredQuery.onEntity("Movies", ODataProtocol.V4);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", ODataProtocol.V4);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", ODataProtocol.V4);
final StructuredQuery subQuery3 = StructuredQuery.asNestedQueryOnProperty("relatedBook", ODataProtocol.V4);
final StructuredQuery query = StructuredQuery.onEntity("Movies", V4);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V4);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", V4);
final StructuredQuery subQuery3 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V4);
subQuery2.select(subQuery3);
subQuery1.select(subQuery2);
query.select(subQuery1);
assertThat(query.getQueryString()).isEqualTo("$expand=relatedBook($expand=relatedMovies($expand=relatedBook))");
}

@Test
void testV4QueryExpandWithOptions()
{
final StructuredQuery query = StructuredQuery.onEntity("Movies", V4),
subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V4).skip(3).top(10),
subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", V4).orderBy("Duration", Order.DESC),
subQuery3 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V4).withInlineCount();
subQuery2.select(subQuery3);
subQuery1.select(subQuery2);
query.select(subQuery1);
assertThat(query.getQueryString())
.isEqualTo(
"$expand=relatedBook($expand=relatedMovies($expand=relatedBook($count=true);$orderby=Duration desc);$top=10;$skip=3)");
}

@Test
void testV4QuerySelectAndFilter()
{
final StructuredQuery query = StructuredQuery.onEntity("Movies", ODataProtocol.V4);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", ODataProtocol.V4);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", ODataProtocol.V4);
final StructuredQuery query = StructuredQuery.onEntity("Movies", V4);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V4);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", V4);
subQuery1.select(subQuery2);
query.select(subQuery1);

Expand All @@ -128,10 +144,10 @@ void testV4QuerySelectAndFilter()
@Test
void testV2QueryExpand()
{
final StructuredQuery query = StructuredQuery.onEntity("Movies", ODataProtocol.V2);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", ODataProtocol.V2);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", ODataProtocol.V2);
final StructuredQuery subQuery3 = StructuredQuery.asNestedQueryOnProperty("relatedBook", ODataProtocol.V2);
final StructuredQuery query = StructuredQuery.onEntity("Movies", V2);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V2);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", V2);
final StructuredQuery subQuery3 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V2);
subQuery2.select(subQuery3);
subQuery1.select(subQuery2);
query.select(subQuery1);
Expand All @@ -143,12 +159,26 @@ void testV2QueryExpand()
*/
}

@Test
void testV2QueryExpandWithOptionsIgnored()
{
final StructuredQuery query = StructuredQuery.onEntity("Movies", V2),
subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V2).skip(3).top(10),
subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", V2).orderBy("Duration", Order.DESC),
subQuery3 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V2).withInlineCount();
subQuery2.select(subQuery3);
subQuery1.select(subQuery2);
query.select(subQuery1);
assertThat(query.getQueryString())
.isEqualTo("$expand=relatedBook,relatedBook/relatedMovies,relatedBook/relatedMovies/relatedBook");
}

@Test
void testV2QuerySelectAndFilter()
{
final StructuredQuery query = StructuredQuery.onEntity("Movies", ODataProtocol.V2);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", ODataProtocol.V2);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", ODataProtocol.V2);
final StructuredQuery query = StructuredQuery.onEntity("Movies", V2);
final StructuredQuery subQuery1 = StructuredQuery.asNestedQueryOnProperty("relatedBook", V2);
final StructuredQuery subQuery2 = StructuredQuery.asNestedQueryOnProperty("relatedMovies", V2);
subQuery1.select(subQuery2);
query.select(subQuery1);

Expand Down Expand Up @@ -178,8 +208,7 @@ void testExceptionWhenUnencodedQueryString()
final String unencodedQuery = "$orderby=name asc,ID";

assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(
() -> new ODataRequestRead(servicePath, entityName, unencodedQuery, ODataProtocol.V4).getRelativeUri());
.isThrownBy(() -> new ODataRequestRead(servicePath, entityName, unencodedQuery, V4).getRelativeUri());
}

@Test
Expand All @@ -192,8 +221,7 @@ void testGuavaUrlEscaperEscapedQueryString()

final String encodedQuery = UrlEscapers.urlFragmentEscaper().escape(unencodedQuery);

final URI relativeUri =
new ODataRequestRead(servicePath, entityName, encodedQuery, ODataProtocol.V4).getRelativeUri();
final URI relativeUri = new ODataRequestRead(servicePath, entityName, encodedQuery, V4).getRelativeUri();

final String expectedUri = "/odata/v4/Service/Authors?$orderby=name%20asc,ID";

Expand All @@ -208,15 +236,14 @@ void testBuildQueryStringWithStructuredQueryV2()

final StructuredQuery structuredQuery =
StructuredQuery
.onEntity(entityName, ODataProtocol.V2)
.onEntity(entityName, V2)
.filter(FieldReference.of("philosphy").equalTo("Yin & Yang"))
.orderBy(OrderExpression.of("name", Order.ASC).and("ID", Order.ASC))
.withInlineCount();

final String encodedQuery = structuredQuery.getEncodedQueryString();

final URI relativeUri =
new ODataRequestRead(servicePath, entityName, encodedQuery, ODataProtocol.V2).getRelativeUri();
final URI relativeUri = new ODataRequestRead(servicePath, entityName, encodedQuery, V2).getRelativeUri();

final String expectedUri =
"/odata/v2/Service/Authors?$filter=(philosphy%20eq%20'Yin%20%26%20Yang')&$orderby=name%20asc,ID%20asc&$inlinecount=allpages";
Expand All @@ -232,15 +259,14 @@ void testBuildQueryStringWithStructuredQueryV4()

final StructuredQuery structuredQuery =
StructuredQuery
.onEntity(entityName, ODataProtocol.V4)
.onEntity(entityName, V4)
.filter(FieldReference.of("philosphy").equalTo("Yin & Yang"))
.orderBy(OrderExpression.of("name", Order.ASC).and("ID", Order.ASC))
.withInlineCount();

final String encodedQuery = structuredQuery.getEncodedQueryString();

final URI relativeUri =
new ODataRequestRead(servicePath, entityName, encodedQuery, ODataProtocol.V4).getRelativeUri();
final URI relativeUri = new ODataRequestRead(servicePath, entityName, encodedQuery, V4).getRelativeUri();

final String expectedUri =
"/odata/v4/Service/Authors?$filter=(philosphy%20eq%20'Yin%20%26%20Yang')&$orderby=name%20asc,ID%20asc&$count=true";
Expand All @@ -257,7 +283,7 @@ void testCustomParameterswithStructuredQuery()

final StructuredQuery structuredQuery =
StructuredQuery
.onEntity(entityName, ODataProtocol.V4)
.onEntity(entityName, V4)
.filter(FieldReference.of("philosophy").equalTo("Yin & Yang"))
.withCustomParameter(customKey, customValue);

Expand All @@ -270,15 +296,15 @@ void testCustomParameterswithStructuredQuery()
@Test
void testCustomParametersOnNestedQuery()
{
final StructuredQuery query = StructuredQuery.asNestedQueryOnProperty("name", ODataProtocol.V4);
final StructuredQuery query = StructuredQuery.asNestedQueryOnProperty("name", V4);

assertThatThrownBy(() -> query.withCustomParameter("key", "value")).isInstanceOf(IllegalStateException.class);
}

@Test
void testCustomParametersWithEmptyKey()
{
final StructuredQuery query = StructuredQuery.onEntity("name", ODataProtocol.V4);
final StructuredQuery query = StructuredQuery.onEntity("name", V4);

assertThatThrownBy(() -> query.withCustomParameter("", "value")).isInstanceOf(IllegalArgumentException.class);
}
Expand All @@ -288,17 +314,13 @@ void testConstructorWithStructuredQuery()
{
final StructuredQuery structuredQuery =
StructuredQuery
.onEntity("Authors", ODataProtocol.V4)
.onEntity("Authors", V4)
.filter(FieldReference.of("philosphy").equalTo("Yin & Yang"))
.orderBy(OrderExpression.of("name", Order.ASC).and("ID", Order.ASC))
.withInlineCount();

final ODataRequestRead expected =
new ODataRequestRead(
"/some/service/path",
"Authors",
structuredQuery.getEncodedQueryString(),
ODataProtocol.V4);
new ODataRequestRead("/some/service/path", "Authors", structuredQuery.getEncodedQueryString(), V4);

final ODataRequestRead actual =
new ODataRequestRead("/some/service/path", new ODataResourcePath(), structuredQuery);
Expand All @@ -310,7 +332,7 @@ void testConstructorWithStructuredQuery()
@Test
void testConstructorWithStructuredQueryDoesNotMutateResourcePath()
{
final StructuredQuery structuredQuery = StructuredQuery.onEntity("Authors", ODataProtocol.V4).withInlineCount();
final StructuredQuery structuredQuery = StructuredQuery.onEntity("Authors", V4).withInlineCount();
final ODataResourcePath resourcePath = ODataResourcePath.of("Authors");

new ODataRequestRead("/some/service/path", resourcePath, structuredQuery);
Expand Down
2 changes: 1 addition & 1 deletion release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

### 📈 Improvements

-
- (Generic OData Client) Allow for parameters in OData v4 expand sub-queries.

### 🐛 Fixed Issues

Expand Down