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());
+  }
+}