diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/ODataInvokeRequestImpl.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/ODataInvokeRequestImpl.java index ac6d1627b..9043fe8bd 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/ODataInvokeRequestImpl.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/communication/request/invoke/ODataInvokeRequestImpl.java @@ -153,7 +153,7 @@ public class ODataInvokeRequestImpl throw new IllegalArgumentException("Only primitive values can be passed via GET"); } - uriBuilder.addParameter(param.getKey(), param.getValue().toString()); + uriBuilder.addParameter(param.getKey(), URIUtils.escape(odataClient.getServiceVersion(), param.getValue())); } try { ((HttpRequestBase) this.request).setURI(uriBuilder.build()); diff --git a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java index 1e0dd5bf5..8dce0618f 100644 --- a/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java +++ b/lib/client-core/src/main/java/org/apache/olingo/client/core/uri/URIUtils.java @@ -27,7 +27,11 @@ import java.net.URI; import java.net.URLEncoder; import java.sql.Timestamp; import java.util.Calendar; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; import java.util.UUID; +import java.util.regex.Pattern; import javax.xml.datatype.Duration; import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; @@ -67,6 +71,8 @@ public final class URIUtils { */ private static final Logger LOG = LoggerFactory.getLogger(URIUtils.class); + private static final Pattern ENUM_VALUE = Pattern.compile("(.+\\.)?.+'.+'"); + private URIUtils() { // Empty private constructor for static utility classes } @@ -274,53 +280,102 @@ public final class URIUtils { Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null), Constants.UTF8)); } + private static String quoteString(final String string, final boolean singleQuoteEscape) + throws UnsupportedEncodingException { + + final String encoded = URLEncoder.encode(string, Constants.UTF8); + return ENUM_VALUE.matcher(string).matches() + ? encoded + : singleQuoteEscape + ? "'" + encoded + "'" + : "\"" + encoded + "\""; + } + /** * Turns primitive values into their respective URI representation. * + * @param version OData protocol version * @param obj primitive value * @return URI representation */ public static String escape(final ODataServiceVersion version, final Object obj) { + return escape(version, obj, true); + } + + private static String escape(final ODataServiceVersion version, final Object obj, final boolean singleQuoteEscape) { String value; try { - value = (obj instanceof Boolean) - ? BooleanUtils.toStringTrueFalse((Boolean) obj) - : (obj instanceof UUID) - ? prefix(version, EdmPrimitiveTypeKind.Guid) - + obj.toString() - + suffix(version, EdmPrimitiveTypeKind.Guid) - : (obj instanceof byte[]) - ? EdmBinary.getInstance().toUriLiteral(Hex.encodeHexString((byte[]) obj)) - : (obj instanceof Timestamp) - ? timestamp(version, (Timestamp) obj) - : (obj instanceof Calendar) - ? calendar(version, (Calendar) obj) - : (obj instanceof Duration) - ? duration(version, (Duration) obj) - : (obj instanceof BigDecimal) - ? EdmDecimal.getInstance().valueToString(obj, null, null, - Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) - + suffix(version, EdmPrimitiveTypeKind.Decimal) - : (obj instanceof Double) - ? EdmDouble.getInstance().valueToString(obj, null, null, - Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) - + suffix(version, EdmPrimitiveTypeKind.Double) - : (obj instanceof Float) - ? EdmSingle.getInstance().valueToString(obj, null, null, - Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) - + suffix(version, EdmPrimitiveTypeKind.Single) - : (obj instanceof Long) - ? EdmInt64.getInstance().valueToString(obj, null, null, - Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) - + suffix(version, EdmPrimitiveTypeKind.Int64) - : (obj instanceof Geospatial) - ? URLEncoder.encode(EdmPrimitiveTypeFactory.getInstance(((Geospatial) obj).getEdmPrimitiveTypeKind()). - valueToString(obj, null, null, Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null), - Constants.UTF8) - : (obj instanceof String) - ? "'" + URLEncoder.encode((String) obj, Constants.UTF8) + "'" - : obj.toString(); + if (obj == null) { + value = Constants.ATTR_NULL; + } else if (version == ODataServiceVersion.V40 && obj instanceof Collection) { + final StringBuffer buffer = new StringBuffer("["); + for (@SuppressWarnings("unchecked") + final Iterator itor = ((Collection) obj).iterator(); itor.hasNext();) { + buffer.append(escape(version, itor.next(), false)); + if (itor.hasNext()) { + buffer.append(','); + } + } + buffer.append(']'); + + value = buffer.toString(); + } else if (version == ODataServiceVersion.V40 && obj instanceof Map) { + final StringBuffer buffer = new StringBuffer("{"); + for (@SuppressWarnings("unchecked") + final Iterator> itor = + ((Map) obj).entrySet().iterator(); itor.hasNext();) { + + final Map.Entry entry = itor.next(); + buffer.append("\"").append(URLEncoder.encode(entry.getKey().toString(), Constants.UTF8)).append("\""); + buffer.append(':').append(escape(version, entry.getValue(), false)); + + if (itor.hasNext()) { + buffer.append(','); + } + } + buffer.append('}'); + + value = buffer.toString(); + } else { + value = (obj instanceof Boolean) + ? BooleanUtils.toStringTrueFalse((Boolean) obj) + : (obj instanceof UUID) + ? prefix(version, EdmPrimitiveTypeKind.Guid) + + obj.toString() + + suffix(version, EdmPrimitiveTypeKind.Guid) + : (obj instanceof byte[]) + ? EdmBinary.getInstance().toUriLiteral(Hex.encodeHexString((byte[]) obj)) + : (obj instanceof Timestamp) + ? timestamp(version, (Timestamp) obj) + : (obj instanceof Calendar) + ? calendar(version, (Calendar) obj) + : (obj instanceof Duration) + ? duration(version, (Duration) obj) + : (obj instanceof BigDecimal) + ? EdmDecimal.getInstance().valueToString(obj, null, null, + Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) + + suffix(version, EdmPrimitiveTypeKind.Decimal) + : (obj instanceof Double) + ? EdmDouble.getInstance().valueToString(obj, null, null, + Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) + + suffix(version, EdmPrimitiveTypeKind.Double) + : (obj instanceof Float) + ? EdmSingle.getInstance().valueToString(obj, null, null, + Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) + + suffix(version, EdmPrimitiveTypeKind.Single) + : (obj instanceof Long) + ? EdmInt64.getInstance().valueToString(obj, null, null, + Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null) + + suffix(version, EdmPrimitiveTypeKind.Int64) + : (obj instanceof Geospatial) + ? URLEncoder.encode(EdmPrimitiveTypeFactory.getInstance(((Geospatial) obj).getEdmPrimitiveTypeKind()). + valueToString(obj, null, null, Constants.DEFAULT_PRECISION, Constants.DEFAULT_SCALE, null), + Constants.UTF8) + : (obj instanceof String) + ? quoteString((String) obj, singleQuoteEscape) + : obj.toString(); + } } catch (Exception e) { LOG.warn("While escaping '{}', using toString()", obj, e); value = obj.toString(); diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/URIEscapeTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/URIEscapeTest.java new file mode 100644 index 000000000..886d1e8bd --- /dev/null +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/URIEscapeTest.java @@ -0,0 +1,79 @@ +/* + * 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.olingo.client.core.uri; + +import static org.junit.Assert.assertEquals; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Collections; +import org.apache.olingo.client.core.edm.EdmEnumTypeImpl; +import org.apache.olingo.client.core.edm.xml.v4.EnumTypeImpl; +import org.apache.olingo.commons.api.Constants; +import org.apache.olingo.commons.api.edm.EdmEnumType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; +import org.apache.olingo.commons.api.edm.geo.Geospatial; +import org.apache.olingo.commons.api.edm.geo.Point; +import org.junit.Test; + +public class URIEscapeTest { + + @Test + public void _null() { + assertEquals("null", URIUtils.escape(ODataServiceVersion.V40, null)); + } + + @Test + public void _boolean() { + assertEquals("true", URIUtils.escape(ODataServiceVersion.V40, Boolean.TRUE)); + } + + @Test + public void _enum() throws UnsupportedEncodingException { + final EdmEnumType pattern = new EdmEnumTypeImpl(ODataServiceVersion.V40, + null, new FullQualifiedName("Sales", "Pattern"), new EnumTypeImpl()); + + assertEquals(URLEncoder.encode("Sales.Pattern'Yellow'", Constants.UTF8), + URIUtils.escape(ODataServiceVersion.V40, pattern.toUriLiteral("Yellow"))); + } + + @Test + public void geospatial() throws UnsupportedEncodingException { + final Point point = new Point(Geospatial.Dimension.GEOGRAPHY, 0); + point.setX(142.1); + point.setY(64.1); + + assertEquals(URLEncoder.encode("geography'SRID=0;Point(142.1 64.1)'", Constants.UTF8), + URIUtils.escape(ODataServiceVersion.V40, point)); + } + + @Test + public void collection() { + assertEquals("[\"red\",\"green\"]", + URIUtils.escape(ODataServiceVersion.V40, Arrays.asList(new String[] {"red", "green"}))); + } + + @Test + public void complex() { + assertEquals("{\"Name\":\"Value\"}", + URIUtils.escape(ODataServiceVersion.V40, Collections.singletonMap("Name", "Value"))); + } +} diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/v3/FilterFactoryTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/FilterFactoryTest.java similarity index 99% rename from lib/client-core/src/test/java/org/apache/olingo/client/core/v3/FilterFactoryTest.java rename to lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/FilterFactoryTest.java index 9d7e0a3ca..666edf72a 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/v3/FilterFactoryTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/FilterFactoryTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.client.core.v3; +package org.apache.olingo.client.core.uri.v3; import org.apache.olingo.client.api.v3.ODataClient; import org.apache.olingo.client.api.uri.URIFilter; diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/v3/URIBuilderTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java similarity index 99% rename from lib/client-core/src/test/java/org/apache/olingo/client/core/v3/URIBuilderTest.java rename to lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java index 1838cf8fd..78badefd2 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/v3/URIBuilderTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v3/URIBuilderTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.client.core.v3; +package org.apache.olingo.client.core.uri.v3; import static org.junit.Assert.assertEquals; diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/FilterFactoryTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/FilterFactoryTest.java similarity index 98% rename from lib/client-core/src/test/java/org/apache/olingo/client/core/v4/FilterFactoryTest.java rename to lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/FilterFactoryTest.java index fd2a69694..06a07e6b7 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/FilterFactoryTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/FilterFactoryTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.client.core.v4; +package org.apache.olingo.client.core.uri.v4; import org.apache.olingo.client.api.v4.ODataClient; import org.apache.olingo.client.api.uri.URIFilter; diff --git a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/URIBuilderTest.java b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java similarity index 99% rename from lib/client-core/src/test/java/org/apache/olingo/client/core/v4/URIBuilderTest.java rename to lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java index 868b363c8..1518cf0fa 100644 --- a/lib/client-core/src/test/java/org/apache/olingo/client/core/v4/URIBuilderTest.java +++ b/lib/client-core/src/test/java/org/apache/olingo/client/core/uri/v4/URIBuilderTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.olingo.client.core.v4; +package org.apache.olingo.client.core.uri.v4; import java.net.URI; import java.net.URISyntaxException;