You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2015/06/15 10:12:08 UTC
olingo-odata4 git commit: PATCH
Repository: olingo-odata4
Updated Branches:
refs/heads/master 5501e8e33 -> 3b506eaad
PATCH
Signed-off-by: Christian Amend <ch...@sap.com>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/3b506eaa
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/3b506eaa
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/3b506eaa
Branch: refs/heads/master
Commit: 3b506eaade59d5596a2465b7ec0686d6c425a172
Parents: 5501e8e
Author: x.x@sap.com <x....@sap.com>
Authored: Wed Jun 10 22:02:36 2015 +0200
Committer: Christian Amend <ch...@sap.com>
Committed: Mon Jun 15 10:05:36 2015 +0200
----------------------------------------------------------------------
.../org/apache/olingo/server/api/OData.java | 8 +
.../olingo/server/api/prefer/Preferences.java | 107 ++++++++++++
.../server/api/prefer/PreferencesApplied.java | 164 ++++++++++++++++++
.../api/prefer/PreferencesAppliedTest.java | 67 ++++++++
.../apache/olingo/server/core/ODataImpl.java | 8 +
.../olingo/server/core/prefer/PreferParser.java | 150 +++++++++++++++++
.../server/core/prefer/PreferencesImpl.java | 117 +++++++++++++
.../server/core/prefer/PreferencesTest.java | 168 +++++++++++++++++++
8 files changed, 789 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
index 3a9c1cb..e9dc225 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/OData.java
@@ -18,6 +18,7 @@
*/
package org.apache.olingo.server.api;
+import java.util.Collection;
import java.util.List;
import org.apache.olingo.commons.api.ODataRuntimeException;
@@ -31,6 +32,7 @@ import org.apache.olingo.server.api.deserializer.ODataDeserializer;
import org.apache.olingo.server.api.edmx.EdmxReference;
import org.apache.olingo.server.api.etag.ETagHelper;
import org.apache.olingo.server.api.etag.ServiceMetadataETagSupport;
+import org.apache.olingo.server.api.prefer.Preferences;
import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
import org.apache.olingo.server.api.serializer.ODataSerializer;
import org.apache.olingo.server.api.serializer.SerializerException;
@@ -135,4 +137,10 @@ public abstract class OData {
* It can be used in Processor implementations.
*/
public abstract ETagHelper createETagHelper();
+
+ /**
+ * Creates a new Preferences object out of Prefer HTTP request headers.
+ * It can be used in Processor implementations.
+ */
+ public abstract Preferences createPreferences(Collection<String> preferHeaders);
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/Preferences.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/Preferences.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/Preferences.java
new file mode 100644
index 0000000..91f0d45
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/Preferences.java
@@ -0,0 +1,107 @@
+/*
+ * 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.server.api.prefer;
+
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * Provides access methods to the preferences set in Prefer HTTP request headers
+ * as described in <a href="https://www.ietf.org/rfc/rfc7240.txt">RFC 7240</a>.
+ * Preferences defined in the OData standard can be accessed with named methods.
+ */
+public interface Preferences {
+
+ /**
+ * Gets named preference. Names are case insensitive.
+ * @param name name of the preference
+ * @return {@link Preference} or <code>null</code> if no such preference has been set
+ */
+ public Preference getPreference(String name);
+
+ /** Whether the preference <code>odata.allow-entityreferences</code> has been set. */
+ public boolean hasAllowEntityReferences();
+
+ /**
+ * Gets the value of the <code>url</code> parameter of the preference
+ * <code>odata.callback</code> or <code>null</code> if not set or the URI is not valid.
+ * @return the callback URI
+ */
+ public URI getCallback();
+
+ /** Whether the preference <code>odata.continue-on-error</code> has been set. */
+ public boolean hasContinueOnError();
+
+ /**
+ * Gets the value of the preference <code>odata.maxpagesize</code>
+ * or <code>null</code> if not set or an invalid value has been set.
+ * @return the page size for server-driven paging
+ */
+ public Integer getMaxPageSize();
+
+ /** Whether the preference <code>odata.track-changes</code> has been set. */
+ public boolean hasTrackChanges();
+
+ public enum Return { REPRESENTATION, MINIMAL }
+ /**
+ * Gets the value of the preference <code>return</code> or <code>null</code> if not set
+ * or the value is not valid.
+ */
+ public Return getReturn();
+
+ /** Whether the preference <code>respond-async</code> has been set. */
+ public boolean hasRespondAsync();
+
+ /**
+ * Gets the value of the preference <code>wait</code> or <code>null</code> if not set
+ * or the value is not valid.
+ * @return the number of seconds the client is prepared to wait for the service
+ * to process the request synchronously
+ */
+ public Integer getWait();
+
+ /** Generic preference object. */
+ public class Preference {
+ private final String value;
+ private final Map<String, String> parameters;
+
+ public Preference(final String value, final Map<String, String> parameters) {
+ this.value = value;
+ this.parameters = parameters;
+ }
+
+ /**
+ * Gets the value of the preference. It may be <code>null</code> if the preference has
+ * no value; this is not the same as the preference not set.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Gets the parameters of the preference. The value of a parameter may be
+ * <code>null</code> if the parameter has no value; this is not the same as
+ * the parameter not set. Parameter names are all lowercase.
+ * @return a map from parameter names to parameter values
+ */
+ public Map<String, String> getParameters() {
+ return parameters;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/PreferencesApplied.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/PreferencesApplied.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/PreferencesApplied.java
new file mode 100644
index 0000000..fd89a3a
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/prefer/PreferencesApplied.java
@@ -0,0 +1,164 @@
+/*
+ * 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.server.api.prefer;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.olingo.server.api.prefer.Preferences.Return;
+
+/**
+ * Provides methods to set values suitable for the Preference-Applied HTTP response header
+ * as described in <a href="https://www.ietf.org/rfc/rfc7240.txt">RFC 7240</a>.
+ * There are named methods for preferences defined in the OData standard.
+ */
+public class PreferencesApplied {
+
+ private Map<String, String> applied;
+
+ private PreferencesApplied() {
+ applied = new LinkedHashMap<String, String>();
+ }
+
+ /**
+ * Gets the applied preferences.
+ * @return a map from preference names to preference values
+ */
+ public Map<String, String> getAppliedPreferences() {
+ return Collections.unmodifiableMap(applied);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ for (final String name : applied.keySet()) {
+ if (result.length() > 0) {
+ result.append(',').append(' ');
+ }
+ result.append(name);
+ if (applied.get(name) != null) {
+ result.append('=').append('"')
+ .append(applied.get(name).replaceAll("\\\\|\"", "\\\\$0"))
+ .append('"');
+ }
+ }
+ return result.toString();
+ }
+
+ /** Initializes the builder. */
+ public static Builder with() {
+ return new Builder();
+ }
+
+ /** Builder of OData serializer options. */
+ public static final class Builder {
+
+ private static final String ALLOW_ENTITY_REFERENCES = "odata.allow-entityreferences";
+ private static final String CALLBACK = "odata.callback";
+ private static final String CONTINUE_ON_ERROR = "odata.continue-on-error";
+ // private static final String INCLUDE_ANNOTATIONS = "odata.include-annotations";
+ private static final String MAX_PAGE_SIZE = "odata.maxpagesize";
+ private static final String TRACK_CHANGES = "odata.track-changes";
+ private static final String RETURN = "return";
+ private static final String RESPOND_ASYNC = "respond-async";
+ private static final String WAIT = "wait";
+
+ private final PreferencesApplied preferencesApplied;
+
+ private Builder() {
+ preferencesApplied = new PreferencesApplied();
+ }
+
+ /** Sets <code>odata.allow-entityreferences</code>. */
+ public Builder allowEntityReferences() {
+ add(ALLOW_ENTITY_REFERENCES, null);
+ return this;
+ }
+
+ /** Sets <code>odata.callback</code>. */
+ public Builder callback() {
+ add(CALLBACK, null);
+ return this;
+ }
+
+ /** Sets <code>odata.continue-on-error</code>. */
+ public Builder continueOnError() {
+ add(CONTINUE_ON_ERROR, null);
+ return this;
+ }
+
+ /** Sets the value of the applied preference <code>odata.maxpagesize</code>. */
+ public Builder maxPageSize(final Integer maxPageSize) {
+ add(MAX_PAGE_SIZE, Integer.toString(maxPageSize));
+ return this;
+ }
+
+ /** Sets <code>odata.track-changes</code>. */
+ public Builder trackChanges() {
+ add(TRACK_CHANGES, null);
+ return this;
+ }
+
+ /** Sets the value of the applied preference <code>return</code>. */
+ public Builder returnRepresentation(final Return returnRepresentation) {
+ add(RETURN, returnRepresentation.name().toLowerCase(Locale.ROOT));
+ return this;
+ }
+
+ /** Sets <code>odata.respond-async</code>. */
+ public Builder respondAsync() {
+ add(RESPOND_ASYNC, null);
+ return this;
+ }
+
+ /** Sets the value of the applied preference <code>wait</code>. */
+ public Builder waitPreference(final Integer wait) {
+ add(WAIT, Integer.toString(wait));
+ return this;
+ }
+
+ /**
+ * Sets an arbitrary preference as applied.
+ * The preference name is converted to lowercase.
+ * The value of this preference may be <code>null</code>.
+ * Name and value are not checked for validity.
+ * @param name preference name
+ * @param value preference value
+ */
+ public Builder preference(final String name, final String value) {
+ if (name != null) {
+ add(name.toLowerCase(Locale.ROOT), value);
+ }
+ return this;
+ }
+
+ /** Builds the applied preferences. */
+ public PreferencesApplied build() {
+ return preferencesApplied;
+ }
+
+ private void add(final String name, final String value) {
+ if (!preferencesApplied.applied.containsKey(name)) {
+ preferencesApplied.applied.put(name, value);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-api/src/test/java/org/apache/olingo/server/api/prefer/PreferencesAppliedTest.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/test/java/org/apache/olingo/server/api/prefer/PreferencesAppliedTest.java b/lib/server-api/src/test/java/org/apache/olingo/server/api/prefer/PreferencesAppliedTest.java
new file mode 100644
index 0000000..0d5edf9
--- /dev/null
+++ b/lib/server-api/src/test/java/org/apache/olingo/server/api/prefer/PreferencesAppliedTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.server.api.prefer;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.olingo.server.api.prefer.Preferences.Return;
+import org.junit.Test;
+
+public class PreferencesAppliedTest {
+
+ @Test
+ public void empty() {
+ assertEquals("", PreferencesApplied.with().build().toString());
+ }
+
+ @Test
+ public void all() {
+ assertEquals("odata.allow-entityreferences, odata.callback,"
+ + " odata.continue-on-error, odata.include-annotations=\"*\", odata.maxpagesize=\"42\","
+ + " odata.track-changes, return=\"representation\", respond-async, wait=\"12345\"",
+ PreferencesApplied.with().allowEntityReferences().callback().continueOnError()
+ .preference("odata.include-annotations", "*").maxPageSize(42).trackChanges()
+ .returnRepresentation(Return.REPRESENTATION).respondAsync().waitPreference(12345)
+ .build().toString());
+ }
+
+ @Test
+ public void caseSensitivity() {
+ assertEquals("odata.include-annotations=\"*\", odata.maxpagesize=\"255\"",
+ PreferencesApplied.with()
+ .preference("OData.Include-Annotations", "*").maxPageSize(0xFF)
+ .build().toString());
+ }
+
+ @Test
+ public void multipleValues() {
+ assertEquals("return=\"minimal\", wait=\"1\"",
+ PreferencesApplied.with()
+ .returnRepresentation(Return.MINIMAL).returnRepresentation(Return.REPRESENTATION)
+ .preference(null, null).preference(null, "nullValue")
+ .waitPreference(1).waitPreference(2).waitPreference(3)
+ .build().toString());
+ }
+
+ @Test
+ public void quotedValue() {
+ assertEquals("strangepreference=\"x\\\\y,\\\"abc\\\"z\"",
+ PreferencesApplied.with().preference("strangePreference", "x\\y,\"abc\"z").build().toString());
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
index 57cceaf..35c1ccf 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataImpl.java
@@ -18,6 +18,7 @@
*/
package org.apache.olingo.server.core;
+import java.util.Collection;
import java.util.List;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
@@ -34,6 +35,7 @@ import org.apache.olingo.server.api.deserializer.ODataDeserializer;
import org.apache.olingo.server.api.edmx.EdmxReference;
import org.apache.olingo.server.api.etag.ETagHelper;
import org.apache.olingo.server.api.etag.ServiceMetadataETagSupport;
+import org.apache.olingo.server.api.prefer.Preferences;
import org.apache.olingo.server.api.serializer.FixedFormatSerializer;
import org.apache.olingo.server.api.serializer.ODataSerializer;
import org.apache.olingo.server.api.serializer.SerializerException;
@@ -41,6 +43,7 @@ import org.apache.olingo.server.api.uri.UriHelper;
import org.apache.olingo.server.core.deserializer.FixedFormatDeserializerImpl;
import org.apache.olingo.server.core.deserializer.json.ODataJsonDeserializer;
import org.apache.olingo.server.core.etag.ETagHelperImpl;
+import org.apache.olingo.server.core.prefer.PreferencesImpl;
import org.apache.olingo.server.core.serializer.FixedFormatSerializerImpl;
import org.apache.olingo.server.core.serializer.json.ODataJsonSerializer;
import org.apache.olingo.server.core.serializer.xml.ODataXmlSerializerImpl;
@@ -128,4 +131,9 @@ public class ODataImpl extends OData {
return new ETagHelperImpl();
}
+ @Override
+ public Preferences createPreferences(final Collection<String> preferHeaders) {
+ return new PreferencesImpl(preferHeaders);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferParser.java
new file mode 100644
index 0000000..a3a2a42
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferParser.java
@@ -0,0 +1,150 @@
+/*
+ * 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.server.core.prefer;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.olingo.server.api.prefer.Preferences.Preference;
+
+/**
+ * <p>Parses the values of <code>Prefer</code> HTTP header fields.</p>
+ * <p>See <a href="https://www.ietf.org/rfc/rfc7240.txt">RFC 7240</a> for details;
+ * there the following grammar is defined:</p>
+ * <pre>
+ * Prefer = "Prefer" ":" 1#preference
+ * preference = token [ BWS "=" BWS word ] *( OWS ";" [ OWS parameter ] )
+ * parameter = token [ BWS "=" BWS word ]
+ * token = 1*tchar
+ * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
+ / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
+ * word = token / quoted-string
+ * quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
+ * qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / %x80-FF
+ * quoted-pair = "\" ( HTAB / SP / %x21-7E / %x80-FF )
+ * OWS = *( SP / HTAB ) ; optional whitespace
+ * BWS = OWS ; "bad" whitespace
+ * </pre>
+ * <p>Values with illegal syntax do not contribute to the result but no exception is thrown.</p>
+ */
+public class PreferParser {
+
+ private static final String token = "(?:[-!#$%&'*+.^_`|~]|\\w)+";
+ private static final String quotedString = "(?:\"(?:[\\t !#-\\[\\]-~\\x80-\\xFF]|"
+ + "(?:\\\\[\\t !-~\\x80-\\xFF]))*\")";
+ private static final String namedValue =
+ "(" + token + ")(?:\\s*=\\s*(" + token + "|" + quotedString + "))?";
+ private static final Pattern PREFERENCE = Pattern.compile("\\s*(,\\s*)+|"
+ + "(?:" + namedValue + "((?:\\s*;\\s*(?:" + namedValue + ")?)*))");
+ private static final Pattern PARAMETER = Pattern.compile("\\s*(;\\s*)+|(?:" + namedValue + ")");
+
+ protected static Map<String, Preference> parse(final Collection<String> values) {
+ if (values == null || values.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, Preference> result = new HashMap<String, Preference>();
+ for (final String value : values) {
+ if (value != null && !value.isEmpty()) {
+ parse(value, result);
+ }
+ }
+ return result;
+ }
+
+ private static void parse(final String value, Map<String, Preference> result) {
+ Map<String, Preference> partResult = new HashMap<String, Preference>();
+ String separator = "";
+ int start = 0;
+ Matcher matcher = PREFERENCE.matcher(value.trim());
+ while (matcher.find() && matcher.start() == start) {
+ start = matcher.end();
+ if (matcher.group(1) != null) {
+ separator = matcher.group(1);
+ } else if (separator != null) {
+ final String name = matcher.group(2).toLowerCase(Locale.ROOT);
+ // RFC 7240:
+ // If any preference is specified more than once, only the first instance is to be
+ // considered. All subsequent occurrences SHOULD be ignored without signaling
+ // an error or otherwise altering the processing of the request.
+ if (!partResult.containsKey(name)) {
+ final String preferenceValue = getValue(matcher.group(3));
+ final Map<String, String> parameters =
+ matcher.group(4) == null || matcher.group(4).isEmpty() ? null :
+ parseParameters(matcher.group(4));
+ partResult.put(name, new Preference(preferenceValue, parameters));
+ }
+ separator = null;
+ } else {
+ return;
+ }
+ }
+ if (matcher.hitEnd()) {
+ // Here we also have to keep already existing preferences.
+ for (final String key : partResult.keySet()) {
+ if (!result.containsKey(key)) {
+ result.put(key, partResult.get(key));
+ }
+ }
+ }
+ }
+
+ private static Map<String, String> parseParameters(final String parameters) {
+ Map<String, String> result = new HashMap<String, String>();
+ String separator = "";
+ int start = 0;
+ Matcher matcher = PARAMETER.matcher(parameters.trim());
+ while (matcher.find() && matcher.start() == start) {
+ start = matcher.end();
+ if (matcher.group(1) != null) {
+ separator = matcher.group(1);
+ } else if (separator != null) {
+ final String name = matcher.group(2).toLowerCase(Locale.ROOT);
+ // We have to keep already existing parameters.
+ if (!result.containsKey(name)) {
+ result.put(name, getValue(matcher.group(3)));
+ }
+ separator = null;
+ } else {
+ return null;
+ }
+ }
+ return matcher.hitEnd() ? Collections.unmodifiableMap(result) : null;
+ }
+
+ private static String getValue(final String value) {
+ if (value == null) {
+ return null;
+ }
+ String result = value;
+ if (value.startsWith("\"")) {
+ result = value.substring(1, value.length() - 1);
+ }
+ // Unquote backslash-quoted characters.
+ if (result.indexOf('\\') >= 0) {
+ result = result.replaceAll("\\\\(.)", "$1");
+ }
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java
new file mode 100644
index 0000000..9661cd3
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/prefer/PreferencesImpl.java
@@ -0,0 +1,117 @@
+/*
+ * 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.server.core.prefer;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.olingo.server.api.prefer.Preferences;
+
+/**
+ * Provides access methods to the preferences set in the Prefer HTTP request
+ * header as described in <a href="https://www.ietf.org/rfc/rfc7240.txt">RFC 7240</a>.
+ * Preferences defined in the OData standard can be accessed with named methods.
+ */
+public class PreferencesImpl implements Preferences {
+
+ private static final String ALLOW_ENTITY_REFERENCES = "odata.allow-entityreferences";
+ private static final String CALLBACK = "odata.callback";
+ private static final String CONTINUE_ON_ERROR = "odata.continue-on-error";
+ // private static final String INCLUDE_ANNOTATIONS = "odata.include-annotations";
+ private static final String MAX_PAGE_SIZE = "odata.maxpagesize";
+ private static final String TRACK_CHANGES = "odata.track-changes";
+ private static final String RETURN = "return";
+ private static final String RESPOND_ASYNC = "respond-async";
+ private static final String WAIT = "wait";
+
+ private static final String URL = "url"; // parameter name for odata.callback
+
+ private final Map<String, Preference> preferences;
+
+ public PreferencesImpl(final Collection<String> preferHeaders) {
+ preferences = PreferParser.parse(preferHeaders);
+ }
+
+ public Preference getPreference(final String name) {
+ return preferences.get(name.toLowerCase(Locale.ROOT));
+ }
+
+ public boolean hasAllowEntityReferences() {
+ return preferences.containsKey(ALLOW_ENTITY_REFERENCES);
+ }
+
+ public URI getCallback() {
+ if (preferences.containsKey(CALLBACK)
+ && preferences.get(CALLBACK).getParameters() != null
+ && preferences.get(CALLBACK).getParameters().get(URL) != null) {
+ try {
+ return URI.create(preferences.get(CALLBACK).getParameters().get(URL));
+ } catch (final IllegalArgumentException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public boolean hasContinueOnError() {
+ return preferences.containsKey(CONTINUE_ON_ERROR);
+ }
+
+ public Integer getMaxPageSize() {
+ return getNonNegativeIntegerPreference(MAX_PAGE_SIZE);
+ }
+
+ public boolean hasTrackChanges() {
+ return preferences.containsKey(TRACK_CHANGES);
+ }
+
+ public Return getReturn() {
+ if (preferences.containsKey(RETURN)) {
+ final String value = preferences.get(RETURN).getValue();
+ if (Return.REPRESENTATION.toString().toLowerCase(Locale.ROOT).equals(value)) {
+ return Return.REPRESENTATION;
+ } else if (Return.MINIMAL.toString().toLowerCase(Locale.ROOT).equals(value)) {
+ return Return.MINIMAL;
+ }
+ }
+ return null;
+ }
+
+ public boolean hasRespondAsync() {
+ return preferences.containsKey(RESPOND_ASYNC);
+ }
+
+ public Integer getWait() {
+ return getNonNegativeIntegerPreference(WAIT);
+ }
+
+ private Integer getNonNegativeIntegerPreference(final String name) {
+ if (preferences.containsKey(name) && preferences.get(name).getValue() != null) {
+ try {
+ final Integer result = Integer.valueOf(preferences.get(name).getValue());
+ return result < 0 ? null : result;
+ } catch (final NumberFormatException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/3b506eaa/lib/server-core/src/test/java/org/apache/olingo/server/core/prefer/PreferencesTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/prefer/PreferencesTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/prefer/PreferencesTest.java
new file mode 100644
index 0000000..b0e8d74
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/prefer/PreferencesTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.server.core.prefer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.olingo.server.api.prefer.Preferences;
+import org.apache.olingo.server.api.prefer.Preferences.Preference;
+import org.apache.olingo.server.api.prefer.Preferences.Return;
+import org.junit.Test;
+
+public class PreferencesTest {
+
+ @Test
+ public void empty() {
+ final Preferences preferences = new PreferencesImpl(null);
+ assertFalse(preferences.hasAllowEntityReferences());
+ assertNull(preferences.getCallback());
+ assertFalse(preferences.hasContinueOnError());
+ assertNull(preferences.getMaxPageSize());
+ assertFalse(preferences.hasTrackChanges());
+ assertNull(preferences.getReturn());
+ assertFalse(preferences.hasRespondAsync());
+ assertNull(preferences.getWait());
+ }
+
+ @Test
+ public void all() {
+ final Preferences preferences = new PreferencesImpl(Collections.singleton(
+ "odata.allow-entityreferences, odata.callback;url=\"callbackURI\","
+ + "odata.continue-on-error, odata.include-annotations=\"*\", odata.maxpagesize=42,"
+ + "odata.track-changes, return=representation, respond-async, wait=12345"));
+ assertTrue(preferences.hasAllowEntityReferences());
+ assertEquals(URI.create("callbackURI"), preferences.getCallback());
+ assertNotNull(preferences.getPreference("odata.callback"));
+ assertNull(preferences.getPreference("odata.callback").getValue());
+ assertEquals("callbackURI", preferences.getPreference("odata.callback").getParameters().get("url"));
+ assertTrue(preferences.hasContinueOnError());
+ assertEquals("*", preferences.getPreference("odata.Include-Annotations").getValue());
+ assertEquals(Integer.valueOf(42), preferences.getMaxPageSize());
+ assertEquals("42", preferences.getPreference("odata.MaxPageSize").getValue());
+ assertTrue(preferences.hasTrackChanges());
+ assertEquals(Return.REPRESENTATION, preferences.getReturn());
+ assertTrue(preferences.hasRespondAsync());
+ assertEquals(Integer.valueOf(12345), preferences.getWait());
+ }
+
+ @Test
+ public void caseSensitivity() {
+ final Preferences preferences = new PreferencesImpl(Collections.singleton(
+ "OData.Callback;URL=\"callbackURI\", return=REPRESENTATION, Wait=42"));
+ assertEquals(URI.create("callbackURI"), preferences.getCallback());
+ assertNull(preferences.getReturn());
+ assertEquals(Integer.valueOf(42), preferences.getWait());
+ }
+
+ @Test
+ public void multipleValues() {
+ final Preferences preferences = new PreferencesImpl(Collections.singleton(
+ ",return=minimal, ,, return=representation, wait=1, wait=2, wait=3,"));
+ assertEquals(Return.MINIMAL, preferences.getReturn());
+ assertEquals(Integer.valueOf(1), preferences.getWait());
+ }
+
+ @Test
+ public void multipleValuesDifferentHeaders() {
+ final Preferences preferences = new PreferencesImpl(Arrays.asList(
+ null, "",
+ "return=representation, wait=1",
+ "return=minimal, wait=2",
+ "wait=3"));
+ assertEquals(Return.REPRESENTATION, preferences.getReturn());
+ assertEquals(Integer.valueOf(1), preferences.getWait());
+ }
+
+ @Test
+ public void multipleParameters() {
+ final Preferences preferences = new PreferencesImpl(Collections.singleton(
+ "preference=a;;b=c; d = e; f;; ; g; h=\"i\";, wait=42"));
+ final Preference preference = preferences.getPreference("preference");
+ assertEquals("a", preference.getValue());
+ final Map<String, String> parameters = preference.getParameters();
+ assertEquals(5, parameters.size());
+ assertEquals("c", parameters.get("b"));
+ assertEquals("e", parameters.get("d"));
+ assertTrue(parameters.containsKey("f"));
+ assertNull(parameters.get("f"));
+ assertTrue(parameters.containsKey("g"));
+ assertNull(parameters.get("g"));
+ assertEquals("i", parameters.get("h"));
+ assertEquals(Integer.valueOf(42), preferences.getWait());
+ }
+
+ @Test
+ public void quotedValue() {
+ final Preferences preferences = new PreferencesImpl(Collections.singleton(
+ "strangePreference=\"x\\\\y,\\\"abc\\\"z\", wait=42"));
+ assertEquals("x\\y,\"abc\"z", preferences.getPreference("strangePreference").getValue());
+ assertEquals(Integer.valueOf(42), preferences.getWait());
+ }
+
+ @Test
+ public void specialCharacters() {
+ final Preferences preferences = new PreferencesImpl(Collections.singleton(
+ "!#$%&'*+-.^_`|~ = \"!#$%&'()*+,-./:;<=>?@[]^_`{|}~¡\u00FF\", wait=42"));
+ assertEquals("!#$%&'()*+,-./:;<=>?@[]^_`{|}~¡\u00FF",
+ preferences.getPreference("!#$%&'*+-.^_`|~").getValue());
+ assertEquals(Integer.valueOf(42), preferences.getWait());
+ }
+
+ @Test
+ public void wrongContent() {
+ final Preferences preferences = new PreferencesImpl(Arrays.asList(
+ "odata.callback;url=\":\"",
+ "odata.maxpagesize=12345678901234567890",
+ "return=something",
+ "wait=-1"));
+ assertNull(preferences.getCallback());
+ assertEquals(":", preferences.getPreference("odata.callback").getParameters().get("url"));
+ assertNull(preferences.getMaxPageSize());
+ assertEquals("12345678901234567890", preferences.getPreference("odata.maxpagesize").getValue());
+ assertNull(preferences.getReturn());
+ assertEquals("something", preferences.getPreference("return").getValue());
+ assertNull(preferences.getWait());
+ assertEquals("-1", preferences.getPreference("wait").getValue());
+ }
+
+ @Test
+ public void wrongFormat() {
+ final Preferences preferences = new PreferencesImpl(Arrays.asList(
+ "return=, wait=1",
+ "return=;, wait=2",
+ "return=representation=, wait=3",
+ "return=\"representation\"respond-async, wait=4",
+ "respond-async[], wait=5",
+ "odata.callback;=, wait=6",
+ "odata.callback;url=, wait=7",
+ "odata.callback;[], wait=8",
+ "odata.callback;url=\"url\"parameter, wait=9",
+ "wait=10"));
+ assertEquals(Integer.valueOf(10), preferences.getWait());
+ }
+}