olingo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From chri...@apache.org
Subject olingo-odata4 git commit: [OLINGO-663] helper for conditional handling
Date Wed, 03 Jun 2015 09:00:49 GMT
Repository: olingo-odata4
Updated Branches:
  refs/heads/master b8187f530 -> 1f286fd42


[OLINGO-663] helper for conditional handling

Change-Id: I48599dc609bfb53bc2903083abe235458109e20a

Signed-off-by: Christian Amend <christian.amend@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/1f286fd4
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/1f286fd4
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/1f286fd4

Branch: refs/heads/master
Commit: 1f286fd4240a3bbaa9fb7fb50fdb5e44ba9169f0
Parents: b8187f5
Author: Klaus Straubinger <klaus.straubinger@sap.com>
Authored: Wed Jun 3 10:42:54 2015 +0200
Committer: Christian Amend <christian.amend@sap.com>
Committed: Wed Jun 3 11:00:16 2015 +0200

----------------------------------------------------------------------
 .../olingo/server/api/CustomETagSupport.java    |  74 -----------
 .../olingo/server/api/ETagInformation.java      |  74 -----------
 .../org/apache/olingo/server/api/OData.java     |  11 +-
 .../olingo/server/api/ODataHttpHandler.java     |   1 +
 .../server/api/etag/CustomETagSupport.java      |  74 +++++++++++
 .../olingo/server/api/etag/ETagHelper.java      |  66 ++++++++++
 .../api/etag/PreconditionRequiredException.java |  54 ++++++++
 .../apache/olingo/server/core/ETagParser.java   |  86 ------------
 .../olingo/server/core/ODataDispatcher.java     |   2 +
 .../server/core/ODataExceptionHelper.java       |   1 +
 .../apache/olingo/server/core/ODataHandler.java |   3 +-
 .../server/core/ODataHttpHandlerImpl.java       |   2 +-
 .../apache/olingo/server/core/ODataImpl.java    |  23 ++--
 .../core/PreconditionRequiredException.java     |  52 --------
 .../server/core/PreconditionsValidator.java     | 127 ------------------
 .../olingo/server/core/etag/ETagHelperImpl.java |  72 ++++++++++
 .../server/core/etag/ETagInformation.java       |  74 +++++++++++
 .../olingo/server/core/etag/ETagParser.java     |  86 ++++++++++++
 .../core/etag/PreconditionsValidator.java       | 128 ++++++++++++++++++
 .../olingo/server/core/ETagParserTest.java      | 131 -------------------
 .../olingo/server/core/etag/ETagParserTest.java | 129 ++++++++++++++++++
 .../olingo/server/tecsvc/ETagSupport.java       |   2 +-
 .../processor/TechnicalEntityProcessor.java     |  20 +--
 .../TechnicalPrimitiveComplexProcessor.java     |  22 +++-
 .../tecsvc/processor/TechnicalProcessor.java    |  32 -----
 .../server/core/PreconditionsValidatorTest.java |   4 +-
 26 files changed, 733 insertions(+), 617 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-api/src/main/java/org/apache/olingo/server/api/CustomETagSupport.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/CustomETagSupport.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/CustomETagSupport.java
deleted file mode 100644
index 5e4f500..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/CustomETagSupport.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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;
-
-import org.apache.olingo.commons.api.edm.EdmBindingTarget;
-
-/**
- * <p>Processors that would like to support etags for certain entity sets can implement this
- * interface.</p>
- * <p>If implemented this interface can be registered at the ODataHttpHandler. This will result in change request to
- * require an if-match/if-none-match or an if-modified-since/if-unmodified-since header. Otherwise the request will
- * result in a "Precondition Required" response</p>
- */
-public interface CustomETagSupport {
-
-  /**
-   * This method will be called for update requests which target an entity or a property of an entity.
-   * If this method returns true and an header is not specified we will return a "Precondition Required" response.
-   * Validation has to be performed inside the processor methods after the dispatching.
-   * If this method returns false and an header is specified we will ignore the header.
-   * @param entitySetOrSingleton
-   * @return true if the entity set specified needs an if-match/if-none-match header
-   */
-  boolean hasETag(EdmBindingTarget entitySetOrSingleton);
-
-  /**
-   * This method will be called for update requests which target a media entity value.
-   * If this method returns true and an header is not specified we will return a "Precondition Required" response.
-   * Validation has to be performed inside the processor methods after the dispatching.
-   * If this method returns false and an header is specified we will ignore the header.
-   * @param entitySetOrSingleton
-   * @return true if the entity set specified needs an if-match/if-none-match header
-   */
-  boolean hasMediaETag(EdmBindingTarget entitySetOrSingleton);
-
-  /**
-   * Since the Olingo library cannot generate a metadata document etag in a generic way we call this method to retrieve
-   * an application specific etag for the metadata document. If this interface is registered applications can return an
-   * etag or null here to provide caching support for clients. If a client sends a GET request to the metadata document
-   * and this method delivers an etag we will match it to the request. If there has been no modification we will return
-   * a 304 NOT MODIFIED status code. If this interface is not registered or delivers null we just send back the usual
-   * metadata response.
-   * @return the application generated etag for the metadata document
-   */
-  String getMetadataETag();
-
-  /**
-   * Since the Olingo library cannot generate a service document etag in a generic way we call this method to retrieve
-   * an application specific etag for the service document. If this interface is registered applications can return an
-   * etag or null here to provide caching support for clients. If a client sends a GET request to the service document
-   * and this method delivers an etag we will match it to the request. If there has been no modification we will return
-   * a 304 NOT MODIFIED status code. If this interface is not registered or delivers null we just send back the usual
-   * service document response.
-   * @return the application generated etag for the service document
-   */
-  String getServiceDocumentETag();
-
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-api/src/main/java/org/apache/olingo/server/api/ETagInformation.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/ETagInformation.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/ETagInformation.java
deleted file mode 100644
index fa65722..0000000
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/ETagInformation.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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;
-
-import java.util.Collection;
-
-/**
- * Information about the values of an ETag-relevant HTTP header.
- */
-public class ETagInformation {
-  private final boolean all;
-  private final Collection<String> eTags;
-
-  public ETagInformation(final boolean all, final Collection<String> eTags) {
-    this.all = all;
-    this.eTags = eTags;
-  }
-
-  /**
-   * Gets the information whether the values contain "*".
-   */
-  public boolean isAll() {
-    return all;
-  }
-
-  /**
-   * Gets the collection of ETag values found.
-   * It is empty if {@link #isAll()} returns <code>true</code>.
-   */
-  public Collection<String> getETags() {
-    return eTags;
-  }
-
-  /**
-   * <p>Checks whether a given ETag value is matched by this ETag information,
-   * using weak comparison as described in
-   * <a href="https://www.ietf.org/rfc/rfc7232.txt">RFC 7232</a>, section 2.3.2.</p>
-   * <p>If the given value is <code>null</code>, or if this ETag information
-   * does not contain anything, the result is <code>false</code>.</p>
-   * @param eTag the ETag value to match
-   * @return a boolean match result
-   */
-  public boolean isMatchedBy(final String eTag) {
-    if (eTag == null) {
-      return false;
-    } else if (all) {
-      return true;
-    } else {
-      for (final String candidate : eTags) {
-        if ((eTag.startsWith("W/") ? eTag.substring(2) : eTag)
-            .equals(candidate.startsWith("W/") ? candidate.substring(2) : candidate)) {
-          return true;
-        }
-      }
-      return false;
-    }
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/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 2ea9ec7..c8914ef 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,7 +18,6 @@
  */
 package org.apache.olingo.server.api;
 
-import java.util.Collection;
 import java.util.List;
 
 import org.apache.olingo.commons.api.ODataRuntimeException;
@@ -30,6 +29,7 @@ import org.apache.olingo.server.api.deserializer.DeserializerException;
 import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer;
 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.serializer.FixedFormatSerializer;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
@@ -118,11 +118,8 @@ public abstract class OData {
   public abstract EdmPrimitiveType createPrimitiveTypeInstance(EdmPrimitiveTypeKind kind);
 
   /**
-   * Creates ETag information from the values of a HTTP header
-   * containing a list of entity tags or a single star character, i.e.,
-   * <code>If-Match</code> and <code>If-None-Match</code>.
-   * @param values the collection of header values
-   * @return an {@link ETagInformation} instance
+   * Creates a new ETag helper object for performing ETag-related tasks.
+   * It can be used in Processor implementations.
    */
-  public abstract ETagInformation createETagInformation(final Collection<String> values);
+  public abstract ETagHelper createETagHelper();
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
index 3700291..20ed77e 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/ODataHttpHandler.java
@@ -21,6 +21,7 @@ package org.apache.olingo.server.api;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.olingo.server.api.etag.CustomETagSupport;
 import org.apache.olingo.server.api.processor.Processor;
 import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/CustomETagSupport.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/CustomETagSupport.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/CustomETagSupport.java
new file mode 100644
index 0000000..c0dba78
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/CustomETagSupport.java
@@ -0,0 +1,74 @@
+/*
+ * 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.etag;
+
+import org.apache.olingo.commons.api.edm.EdmBindingTarget;
+
+/**
+ * <p>Processors that would like to support etags for certain entity sets can implement this
+ * interface.</p>
+ * <p>If implemented this interface can be registered at the ODataHttpHandler. This will result in change request to
+ * require an if-match/if-none-match or an if-modified-since/if-unmodified-since header. Otherwise the request will
+ * result in a "Precondition Required" response</p>
+ */
+public interface CustomETagSupport {
+
+  /**
+   * This method will be called for update requests which target an entity or a property of an entity.
+   * If this method returns true and an header is not specified we will return a "Precondition Required" response.
+   * Validation has to be performed inside the processor methods after the dispatching.
+   * If this method returns false and an header is specified we will ignore the header.
+   * @param entitySetOrSingleton
+   * @return true if the entity set specified needs an if-match/if-none-match header
+   */
+  boolean hasETag(EdmBindingTarget entitySetOrSingleton);
+
+  /**
+   * This method will be called for update requests which target a media entity value.
+   * If this method returns true and an header is not specified we will return a "Precondition Required" response.
+   * Validation has to be performed inside the processor methods after the dispatching.
+   * If this method returns false and an header is specified we will ignore the header.
+   * @param entitySetOrSingleton
+   * @return true if the entity set specified needs an if-match/if-none-match header
+   */
+  boolean hasMediaETag(EdmBindingTarget entitySetOrSingleton);
+
+  /**
+   * Since the Olingo library cannot generate a metadata document etag in a generic way we call this method to retrieve
+   * an application specific etag for the metadata document. If this interface is registered applications can return an
+   * etag or null here to provide caching support for clients. If a client sends a GET request to the metadata document
+   * and this method delivers an etag we will match it to the request. If there has been no modification we will return
+   * a 304 NOT MODIFIED status code. If this interface is not registered or delivers null we just send back the usual
+   * metadata response.
+   * @return the application generated etag for the metadata document
+   */
+  String getMetadataETag();
+
+  /**
+   * Since the Olingo library cannot generate a service document etag in a generic way we call this method to retrieve
+   * an application specific etag for the service document. If this interface is registered applications can return an
+   * etag or null here to provide caching support for clients. If a client sends a GET request to the service document
+   * and this method delivers an etag we will match it to the request. If there has been no modification we will return
+   * a 304 NOT MODIFIED status code. If this interface is not registered or delivers null we just send back the usual
+   * service document response.
+   * @return the application generated etag for the service document
+   */
+  String getServiceDocumentETag();
+
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/ETagHelper.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/ETagHelper.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/ETagHelper.java
new file mode 100644
index 0000000..ae8b0e2
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/ETagHelper.java
@@ -0,0 +1,66 @@
+/*
+ * 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.etag;
+
+import java.util.Collection;
+
+/**
+ * Used for ETag-related tasks.
+ */
+public interface ETagHelper {
+  /**
+   * <p>Checks the preconditions of a read request with a given ETag value
+   * against the If-Match and If-None-Match HTTP headers.</p> 
+   * <p>If the given ETag value is not matched by the ETag information in the If-Match headers,
+   * and there are ETags in the headers to be matched, a "Precondition Failed" exception is
+   * thrown.</p>
+   * <p>If the given ETag value is matched by the ETag information in the If-None-Match headers,
+   * <code>true</code> is returned, and applications are supposed to return an empty response
+   * with a "Not Modified" status code and the ETag header, <code>false</code> otherwise.</p>
+   * <p>All matching uses weak comparison as described in
+   * <a href="https://www.ietf.org/rfc/rfc7232.txt">RFC 7232</a>, section 2.3.2.</p>
+   * <p>This method does not nothing and returns <code>false</code> if the ETag value is
+   * <code>null</code>.</p>
+   * @param eTag               the ETag value to match
+   * @param ifMatchHeaders     the If-Match header values
+   * @param ifNoneMatchHeaders the If-None-Match header values
+   * @return whether a "Not Modified" response should be used
+   */
+  public boolean checkReadPreconditions(String eTag,
+      Collection<String> ifMatchHeaders, Collection<String> ifNoneMatchHeaders)
+      throws PreconditionRequiredException;
+
+  /**
+   * <p>Checks the preconditions of a change request (with HTTP methods PUT, PATCH, or DELETE)
+   * with a given ETag value against the If-Match and If-None-Match HTTP headers.</p> 
+   * <p>If the given ETag value is not matched by the ETag information in the If-Match headers,
+   * and there are ETags in the headers to be matched, or
+   * if the given ETag value is matched by the ETag information in the If-None-Match headers,
+   *  a "Precondition Failed" exception is thrown.</p>
+   * <p>All matching uses weak comparison as described in
+   * <a href="https://www.ietf.org/rfc/rfc7232.txt">RFC 7232</a>, section 2.3.2.</p>
+   * <p>This method does not nothing if the ETag value is <code>null</code>.</p>
+   * @param eTag               the ETag value to match
+   * @param ifMatchHeaders     the If-Match header values
+   * @param ifNoneMatchHeaders the If-None-Match header values
+   */
+  public void checkChangePreconditions(String eTag,
+      Collection<String> ifMatchHeaders, Collection<String> ifNoneMatchHeaders)
+      throws PreconditionRequiredException;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/PreconditionRequiredException.java
----------------------------------------------------------------------
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/PreconditionRequiredException.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/PreconditionRequiredException.java
new file mode 100644
index 0000000..b49298f
--- /dev/null
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/etag/PreconditionRequiredException.java
@@ -0,0 +1,54 @@
+/*
+ * 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.etag;
+
+import org.apache.olingo.server.api.ODataLibraryException;
+
+public class PreconditionRequiredException extends ODataLibraryException {
+  private static final long serialVersionUID = -8112658467394158700L;
+
+  public static enum MessageKeys implements MessageKey {
+    /** no parameter */
+    MISSING_HEADER,
+    /** no parameter */
+    FAILED,
+    /** no parameter */
+    INVALID_URI;
+
+    @Override
+    public String getKey() {
+      return name();
+    }
+  }
+
+  public PreconditionRequiredException(final String developmentMessage, final MessageKey messageKey,
+      final String... parameters) {
+    super(developmentMessage, messageKey, parameters);
+  }
+
+  public PreconditionRequiredException(final String developmentMessage, final Throwable cause,
+      final MessageKey messageKey, final String... parameters) {
+    super(developmentMessage, cause, messageKey, parameters);
+  }
+
+  @Override
+  protected String getBundleName() {
+    return DEFAULT_SERVER_BUNDLE_NAME;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/ETagParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ETagParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ETagParser.java
deleted file mode 100644
index 20332f6..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ETagParser.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * <p>Parses the values of HTTP header fields that contain a list of entity tags or a
- * single star character, i.e., <code>If-Match</code> and <code>If-None-Match</code>.</p>
- * <p>See <a href="https://www.ietf.org/rfc/rfc7232.txt">RFC 7232</a> for details;
- * there the following grammar is defined:</p>
- * <pre>
- *     If-Match      = "*" / 1#entity-tag
- *     If-None-Match = "*" / 1#entity-tag
- *     entity-tag    = [ weak ] opaque-tag
- *     weak          = %x57.2F ; "W/", case-sensitive
- *     opaque-tag    = DQUOTE *etagc DQUOTE
- *     etagc         = %x21 / %x23-7E / %x80-FF
- * </pre>
- * <p>Values with illegal syntax do not contribute to the result but no exception is thrown.</p>
- */
-public class ETagParser {
-
-  private static final Pattern ETAG = Pattern.compile("\\s*(,\\s*)+|((?:W/)?\"[!#-~\\x80-\\xFF]*\")");
-
-  protected static Collection<String> parse(final Collection<String> values) {
-    if (values == null) {
-      return Collections.<String> emptySet();
-    }
-
-    Set<String> result = new HashSet<String>();
-    for (final String value : values) {
-      final Collection<String> part = parse(value);
-      if (part.size() == 1 && part.iterator().next().equals("*")) {
-        return part;
-      } else {
-        result.addAll(part);
-      }
-    }
-    return result;
-  }
-
-  private static Collection<String> parse(final String value) {
-    if (value.trim().equals("*")) {
-      return Collections.singleton("*");
-    } else {
-      Set<String> result = new HashSet<String>();
-      String separator = "";
-      int start = 0;
-      Matcher matcher = ETAG.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) {
-          result.add(matcher.group(2));
-          separator = null;
-        } else {
-          return Collections.<String> emptySet();
-        }
-      }
-      return matcher.hitEnd() ? result : Collections.<String> emptySet();
-    }
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java
index 30edc5a..d05a98b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataDispatcher.java
@@ -33,6 +33,7 @@ import org.apache.olingo.server.api.ODataApplicationException;
 import org.apache.olingo.server.api.ODataLibraryException;
 import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
+import org.apache.olingo.server.api.etag.PreconditionRequiredException;
 import org.apache.olingo.server.api.processor.ActionComplexCollectionProcessor;
 import org.apache.olingo.server.api.processor.ActionComplexProcessor;
 import org.apache.olingo.server.api.processor.ActionEntityCollectionProcessor;
@@ -67,6 +68,7 @@ import org.apache.olingo.server.api.uri.UriResourcePartTyped;
 import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty;
 import org.apache.olingo.server.api.uri.UriResourceProperty;
 import org.apache.olingo.server.core.batchhandler.BatchHandler;
+import org.apache.olingo.server.core.etag.PreconditionsValidator;
 
 public class ODataDispatcher {
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataExceptionHelper.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataExceptionHelper.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataExceptionHelper.java
index e91e9e2..664b50a 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataExceptionHelper.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataExceptionHelper.java
@@ -26,6 +26,7 @@ import org.apache.olingo.server.api.ODataApplicationException;
 import org.apache.olingo.server.api.ODataLibraryException;
 import org.apache.olingo.server.api.ODataLibraryException.ODataErrorMessage;
 import org.apache.olingo.server.api.deserializer.DeserializerException;
+import org.apache.olingo.server.api.etag.PreconditionRequiredException;
 import org.apache.olingo.server.api.serializer.SerializerException;
 import org.apache.olingo.server.core.uri.parser.UriParserException;
 import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
index b266549..82313f5 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHandler.java
@@ -26,7 +26,6 @@ import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.commons.api.format.ODataFormat;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.CustomETagSupport;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataApplicationException;
 import org.apache.olingo.server.api.ODataLibraryException;
@@ -35,6 +34,8 @@ import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ODataServerError;
 import org.apache.olingo.server.api.ServiceMetadata;
 import org.apache.olingo.server.api.deserializer.DeserializerException;
+import org.apache.olingo.server.api.etag.CustomETagSupport;
+import org.apache.olingo.server.api.etag.PreconditionRequiredException;
 import org.apache.olingo.server.api.processor.DefaultProcessor;
 import org.apache.olingo.server.api.processor.ErrorProcessor;
 import org.apache.olingo.server.api.processor.Processor;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
index 4fb6640..5563f1b 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/ODataHttpHandlerImpl.java
@@ -33,7 +33,6 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.olingo.commons.api.ODataRuntimeException;
 import org.apache.olingo.commons.api.http.HttpHeader;
 import org.apache.olingo.commons.api.http.HttpMethod;
-import org.apache.olingo.server.api.CustomETagSupport;
 import org.apache.olingo.server.api.ODataServerError;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataHttpHandler;
@@ -41,6 +40,7 @@ import org.apache.olingo.server.api.ODataRequest;
 import org.apache.olingo.server.api.ODataResponse;
 import org.apache.olingo.server.api.ODataLibraryException;
 import org.apache.olingo.server.api.ServiceMetadata;
+import org.apache.olingo.server.api.etag.CustomETagSupport;
 import org.apache.olingo.server.api.processor.Processor;
 import org.apache.olingo.server.api.serializer.CustomContentTypeSupport;
 import org.apache.olingo.server.api.serializer.SerializerException;

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/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 e3cbd6d..5c5229f 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,8 +18,6 @@
  */
 package org.apache.olingo.server.core;
 
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 
 import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
@@ -27,7 +25,6 @@ import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
 import org.apache.olingo.commons.api.edm.provider.CsdlEdmProvider;
 import org.apache.olingo.commons.api.format.ODataFormat;
 import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory;
-import org.apache.olingo.server.api.ETagInformation;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataHttpHandler;
 import org.apache.olingo.server.api.ServiceMetadata;
@@ -35,12 +32,14 @@ import org.apache.olingo.server.api.deserializer.DeserializerException;
 import org.apache.olingo.server.api.deserializer.FixedFormatDeserializer;
 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.serializer.FixedFormatSerializer;
 import org.apache.olingo.server.api.serializer.ODataSerializer;
 import org.apache.olingo.server.api.serializer.SerializerException;
 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.serializer.FixedFormatSerializerImpl;
 import org.apache.olingo.server.core.serializer.json.ODataJsonSerializer;
 import org.apache.olingo.server.core.serializer.xml.ODataXmlSerializerImpl;
@@ -96,21 +95,20 @@ public class ODataImpl extends OData {
 
   @Override
   public ODataDeserializer createDeserializer(final ODataFormat format) throws DeserializerException {
-    ODataDeserializer serializer;
+    ODataDeserializer deserializer;
     switch (format) {
     case JSON:
     case JSON_NO_METADATA:
     case JSON_FULL_METADATA:
-      serializer = new ODataJsonDeserializer();
+      deserializer = new ODataJsonDeserializer();
       break;
     case XML:
-      // We do not support xml deserialization right now so this mus lead to an error
+      // We do not support XML deserialization right now so this must lead to an error.
     default:
       throw new DeserializerException("Unsupported format: " + format,
-          SerializerException.MessageKeys.UNSUPPORTED_FORMAT, format.toString());
+          DeserializerException.MessageKeys.UNSUPPORTED_FORMAT, format.toString());
     }
-
-    return serializer;
+    return deserializer;
   }
 
   @Override
@@ -119,10 +117,7 @@ public class ODataImpl extends OData {
   }
 
   @Override
-  public ETagInformation createETagInformation(final Collection<String> values) {
-    final Collection<String> eTags = ETagParser.parse(values);
-    final boolean isAll = eTags.size() == 1 && eTags.iterator().next().equals("*");
-    return new ETagInformation(isAll,
-        isAll ? Collections.<String> emptySet() : Collections.unmodifiableCollection(eTags));
+  public ETagHelper createETagHelper() {
+    return new ETagHelperImpl();
   }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionRequiredException.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionRequiredException.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionRequiredException.java
deleted file mode 100644
index 25ba117..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionRequiredException.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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;
-
-import org.apache.olingo.server.api.ODataLibraryException;
-
-public class PreconditionRequiredException extends ODataLibraryException {
-  private static final long serialVersionUID = -8112658467394158700L;
-
-  public static enum MessageKeys implements MessageKey {
-    /** no parameter */
-    MISSING_HEADER, 
-    /** no parameter */
-    INVALID_URI;
-
-    @Override
-    public String getKey() {
-      return name();
-    }
-  }
-
-  public PreconditionRequiredException(final String developmentMessage, final MessageKey messageKey,
-      final String... parameters) {
-    super(developmentMessage, messageKey, parameters);
-  }
-
-  public PreconditionRequiredException(final String developmentMessage, final Throwable cause,
-      final MessageKey messageKey, final String... parameters) {
-    super(developmentMessage, cause, messageKey, parameters);
-  }
-
-  @Override
-  protected String getBundleName() {
-    return DEFAULT_SERVER_BUNDLE_NAME;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionsValidator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionsValidator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionsValidator.java
deleted file mode 100644
index 490acc1..0000000
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/PreconditionsValidator.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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;
-
-import org.apache.olingo.commons.api.edm.EdmBindingTarget;
-import org.apache.olingo.commons.api.edm.EdmFunctionImport;
-import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
-import org.apache.olingo.server.api.CustomETagSupport;
-import org.apache.olingo.server.api.uri.UriInfo;
-import org.apache.olingo.server.api.uri.UriResource;
-import org.apache.olingo.server.api.uri.UriResourceEntitySet;
-import org.apache.olingo.server.api.uri.UriResourceFunction;
-import org.apache.olingo.server.api.uri.UriResourceNavigation;
-import org.apache.olingo.server.api.uri.UriResourceSingleton;
-
-public class PreconditionsValidator {
-
-  private final CustomETagSupport customETagSupport;
-  private final UriInfo uriInfo;
-  private final String ifMatch;
-  private final String ifNoneMatch;
-
-  public PreconditionsValidator(CustomETagSupport customETagSupport, UriInfo uriInfo, String ifMatch,
-      String ifNoneMatch) {
-    this.customETagSupport = customETagSupport;
-    this.uriInfo = uriInfo;
-    this.ifMatch = ifMatch;
-    this.ifNoneMatch = ifNoneMatch;
-  }
-
-  public void validatePreconditions(boolean isMediaValue) throws PreconditionRequiredException {
-    EdmBindingTarget affectedEntitySetOrSingleton = extractInformation();
-    if (affectedEntitySetOrSingleton != null) {
-      if ((isMediaValue && customETagSupport.hasMediaETag(affectedEntitySetOrSingleton)) ||
-          (!isMediaValue && customETagSupport.hasETag(affectedEntitySetOrSingleton))) {
-        checkETagHeaderPresent();
-      }
-    }
-  }
-
-  private void checkETagHeaderPresent() throws PreconditionRequiredException {
-    if (ifMatch == null && ifNoneMatch == null) {
-      throw new PreconditionRequiredException("Expected an if-match or if-none-match header",
-          PreconditionRequiredException.MessageKeys.MISSING_HEADER);
-    }
-  }
-
-  private EdmBindingTarget extractInformation() throws PreconditionRequiredException {
-    EdmBindingTarget lastFoundEntitySetOrSingleton = null;
-    int counter = 0;
-    for (UriResource uriResourcePart : uriInfo.getUriResourceParts()) {
-      switch (uriResourcePart.getKind()) {
-      case function:
-        lastFoundEntitySetOrSingleton = getEntitySetFromFunctionImport((UriResourceFunction) uriResourcePart);
-        break;
-      case singleton:
-        lastFoundEntitySetOrSingleton = ((UriResourceSingleton) uriResourcePart).getSingleton();
-        break;
-      case entitySet:
-        lastFoundEntitySetOrSingleton = getEntitySet((UriResourceEntitySet) uriResourcePart);
-        break;
-      case navigationProperty:
-        lastFoundEntitySetOrSingleton = getEntitySetFromNavigation(lastFoundEntitySetOrSingleton,
-                (UriResourceNavigation) uriResourcePart);
-        break;
-      case value:
-      case action:
-        // This should not be possible since the URI Parser validates this but to be sure we throw an exception.
-        if (counter != uriInfo.getUriResourceParts().size() - 1) {
-          throw new PreconditionRequiredException("$Value or Action must be the last segment in the URI.",
-              PreconditionRequiredException.MessageKeys.INVALID_URI);
-        }
-        break;
-      default:
-        lastFoundEntitySetOrSingleton = null;
-        break;
-      }
-      if (lastFoundEntitySetOrSingleton == null) {
-        // Once we loose track of the entity set there is no way to retrieve it.
-        break;
-      }
-      counter++;
-    }
-    return lastFoundEntitySetOrSingleton;
-  }
-
-  private EdmBindingTarget getEntitySetFromFunctionImport(UriResourceFunction uriResourceFunction) {
-    EdmFunctionImport functionImport = uriResourceFunction.getFunctionImport();
-    if (functionImport != null && functionImport.getReturnedEntitySet() != null
-        && !uriResourceFunction.isCollection()) {
-      return functionImport.getReturnedEntitySet();
-    }
-    return null;
-  }
-
-  private EdmBindingTarget getEntitySet(UriResourceEntitySet uriResourceEntitySet) {
-    if (!uriResourceEntitySet.isCollection()) {
-      return uriResourceEntitySet.getEntitySet();
-    }
-    return null;
-  }
-
-  private EdmBindingTarget getEntitySetFromNavigation(EdmBindingTarget lastFoundEntitySetOrSingleton,
-                                                      UriResourceNavigation uriResourceNavigation) {
-    if (lastFoundEntitySetOrSingleton != null && !uriResourceNavigation.isCollection()) {
-      EdmNavigationProperty navProp = uriResourceNavigation.getProperty();
-      return lastFoundEntitySetOrSingleton.getRelatedBindingTarget(navProp.getName());
-    }
-    return null;
-  }
-}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagHelperImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagHelperImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagHelperImpl.java
new file mode 100644
index 0000000..1d8c6d6
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagHelperImpl.java
@@ -0,0 +1,72 @@
+/*
+ * 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.etag;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.olingo.server.api.etag.ETagHelper;
+import org.apache.olingo.server.api.etag.PreconditionRequiredException;
+
+public class ETagHelperImpl implements ETagHelper {
+
+  @Override
+  public boolean checkReadPreconditions(final String eTag,
+      final Collection<String> ifMatchHeaders, final Collection<String> ifNoneMatchHeaders)
+      throws PreconditionRequiredException {
+    if (eTag != null) {
+      final ETagInformation ifMatch = createETagInformation(ifMatchHeaders);
+      if (!ifMatch.isMatchedBy(eTag) && !ifMatch.getETags().isEmpty()) {
+        throw new PreconditionRequiredException("The If-Match precondition is not fulfilled.",
+            PreconditionRequiredException.MessageKeys.FAILED);
+      }
+      return createETagInformation(ifNoneMatchHeaders).isMatchedBy(eTag);
+    }
+    return false;
+  }
+
+  @Override
+  public void checkChangePreconditions(final String eTag,
+      final Collection<String> ifMatchHeaders, final Collection<String> ifNoneMatchHeaders)
+      throws PreconditionRequiredException {
+    if (eTag != null) {
+      final ETagInformation ifMatch = createETagInformation(ifMatchHeaders);
+      final ETagInformation ifNoneMatch = createETagInformation(ifNoneMatchHeaders);
+      if (!ifMatch.isMatchedBy(eTag) && !ifMatch.getETags().isEmpty()
+          || ifNoneMatch.isMatchedBy(eTag)) {
+        throw new PreconditionRequiredException("The preconditions are not fulfilled.",
+            PreconditionRequiredException.MessageKeys.FAILED);
+      }
+    }
+  }
+
+  /**
+   * Creates ETag information from the values of a HTTP header
+   * containing a list of entity tags or a single star character, i.e.,
+   * <code>If-Match</code> and <code>If-None-Match</code>.
+   * @param values the collection of header values
+   * @return an {@link ETagInformation} instance
+   */
+  protected ETagInformation createETagInformation(final Collection<String> values) {
+    final Collection<String> eTags = ETagParser.parse(values);
+    final boolean isAll = eTags.size() == 1 && eTags.iterator().next().equals("*");
+    return new ETagInformation(isAll,
+        isAll ? Collections.<String> emptySet() : Collections.unmodifiableCollection(eTags));
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagInformation.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagInformation.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagInformation.java
new file mode 100644
index 0000000..03310ec
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagInformation.java
@@ -0,0 +1,74 @@
+/*
+ * 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.etag;
+
+import java.util.Collection;
+
+/**
+ * Information about the values of an ETag-relevant HTTP header.
+ */
+public class ETagInformation {
+  private final boolean all;
+  private final Collection<String> eTags;
+
+  public ETagInformation(final boolean all, final Collection<String> eTags) {
+    this.all = all;
+    this.eTags = eTags;
+  }
+
+  /**
+   * Gets the information whether the values contain "*".
+   */
+  public boolean isAll() {
+    return all;
+  }
+
+  /**
+   * Gets the collection of ETag values found.
+   * It is empty if {@link #isAll()} returns <code>true</code>.
+   */
+  public Collection<String> getETags() {
+    return eTags;
+  }
+
+  /**
+   * <p>Checks whether a given ETag value is matched by this ETag information,
+   * using weak comparison as described in
+   * <a href="https://www.ietf.org/rfc/rfc7232.txt">RFC 7232</a>, section 2.3.2.</p>
+   * <p>If the given value is <code>null</code>, or if this ETag information
+   * does not contain anything, the result is <code>false</code>.</p>
+   * @param eTag the ETag value to match
+   * @return a boolean match result
+   */
+  public boolean isMatchedBy(final String eTag) {
+    if (eTag == null) {
+      return false;
+    } else if (all) {
+      return true;
+    } else {
+      for (final String candidate : eTags) {
+        if ((eTag.startsWith("W/") ? eTag.substring(2) : eTag)
+            .equals(candidate.startsWith("W/") ? candidate.substring(2) : candidate)) {
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagParser.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagParser.java
new file mode 100644
index 0000000..b8534dd
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/ETagParser.java
@@ -0,0 +1,86 @@
+/*
+ * 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.etag;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>Parses the values of HTTP header fields that contain a list of entity tags or a
+ * single star character, i.e., <code>If-Match</code> and <code>If-None-Match</code>.</p>
+ * <p>See <a href="https://www.ietf.org/rfc/rfc7232.txt">RFC 7232</a> for details;
+ * there the following grammar is defined:</p>
+ * <pre>
+ *     If-Match      = "*" / 1#entity-tag
+ *     If-None-Match = "*" / 1#entity-tag
+ *     entity-tag    = [ weak ] opaque-tag
+ *     weak          = %x57.2F ; "W/", case-sensitive
+ *     opaque-tag    = DQUOTE *etagc DQUOTE
+ *     etagc         = %x21 / %x23-7E / %x80-FF
+ * </pre>
+ * <p>Values with illegal syntax do not contribute to the result but no exception is thrown.</p>
+ */
+public class ETagParser {
+
+  private static final Pattern ETAG = Pattern.compile("\\s*(,\\s*)+|((?:W/)?\"[!#-~\\x80-\\xFF]*\")");
+
+  protected static Collection<String> parse(final Collection<String> values) {
+    if (values == null) {
+      return Collections.<String> emptySet();
+    }
+
+    Set<String> result = new HashSet<String>();
+    for (final String value : values) {
+      final Collection<String> part = parse(value);
+      if (part.size() == 1 && part.iterator().next().equals("*")) {
+        return part;
+      } else {
+        result.addAll(part);
+      }
+    }
+    return result;
+  }
+
+  private static Collection<String> parse(final String value) {
+    if (value.trim().equals("*")) {
+      return Collections.singleton("*");
+    } else {
+      Set<String> result = new HashSet<String>();
+      String separator = "";
+      int start = 0;
+      Matcher matcher = ETAG.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) {
+          result.add(matcher.group(2));
+          separator = null;
+        } else {
+          return Collections.<String> emptySet();
+        }
+      }
+      return matcher.hitEnd() ? result : Collections.<String> emptySet();
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/PreconditionsValidator.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/PreconditionsValidator.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/PreconditionsValidator.java
new file mode 100644
index 0000000..3adf8af
--- /dev/null
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/etag/PreconditionsValidator.java
@@ -0,0 +1,128 @@
+/*
+ * 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.etag;
+
+import org.apache.olingo.commons.api.edm.EdmBindingTarget;
+import org.apache.olingo.commons.api.edm.EdmFunctionImport;
+import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
+import org.apache.olingo.server.api.etag.CustomETagSupport;
+import org.apache.olingo.server.api.etag.PreconditionRequiredException;
+import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.api.uri.UriResource;
+import org.apache.olingo.server.api.uri.UriResourceEntitySet;
+import org.apache.olingo.server.api.uri.UriResourceFunction;
+import org.apache.olingo.server.api.uri.UriResourceNavigation;
+import org.apache.olingo.server.api.uri.UriResourceSingleton;
+
+public class PreconditionsValidator {
+
+  private final CustomETagSupport customETagSupport;
+  private final UriInfo uriInfo;
+  private final String ifMatch;
+  private final String ifNoneMatch;
+
+  public PreconditionsValidator(CustomETagSupport customETagSupport, UriInfo uriInfo, String ifMatch,
+      String ifNoneMatch) {
+    this.customETagSupport = customETagSupport;
+    this.uriInfo = uriInfo;
+    this.ifMatch = ifMatch;
+    this.ifNoneMatch = ifNoneMatch;
+  }
+
+  public void validatePreconditions(boolean isMediaValue) throws PreconditionRequiredException {
+    EdmBindingTarget affectedEntitySetOrSingleton = extractInformation();
+    if (affectedEntitySetOrSingleton != null) {
+      if ((isMediaValue && customETagSupport.hasMediaETag(affectedEntitySetOrSingleton)) ||
+          (!isMediaValue && customETagSupport.hasETag(affectedEntitySetOrSingleton))) {
+        checkETagHeaderPresent();
+      }
+    }
+  }
+
+  private void checkETagHeaderPresent() throws PreconditionRequiredException {
+    if (ifMatch == null && ifNoneMatch == null) {
+      throw new PreconditionRequiredException("Expected an if-match or if-none-match header",
+          PreconditionRequiredException.MessageKeys.MISSING_HEADER);
+    }
+  }
+
+  private EdmBindingTarget extractInformation() throws PreconditionRequiredException {
+    EdmBindingTarget lastFoundEntitySetOrSingleton = null;
+    int counter = 0;
+    for (UriResource uriResourcePart : uriInfo.getUriResourceParts()) {
+      switch (uriResourcePart.getKind()) {
+      case function:
+        lastFoundEntitySetOrSingleton = getEntitySetFromFunctionImport((UriResourceFunction) uriResourcePart);
+        break;
+      case singleton:
+        lastFoundEntitySetOrSingleton = ((UriResourceSingleton) uriResourcePart).getSingleton();
+        break;
+      case entitySet:
+        lastFoundEntitySetOrSingleton = getEntitySet((UriResourceEntitySet) uriResourcePart);
+        break;
+      case navigationProperty:
+        lastFoundEntitySetOrSingleton = getEntitySetFromNavigation(lastFoundEntitySetOrSingleton,
+                (UriResourceNavigation) uriResourcePart);
+        break;
+      case value:
+      case action:
+        // This should not be possible since the URI Parser validates this but to be sure we throw an exception.
+        if (counter != uriInfo.getUriResourceParts().size() - 1) {
+          throw new PreconditionRequiredException("$Value or Action must be the last segment in the URI.",
+              PreconditionRequiredException.MessageKeys.INVALID_URI);
+        }
+        break;
+      default:
+        lastFoundEntitySetOrSingleton = null;
+        break;
+      }
+      if (lastFoundEntitySetOrSingleton == null) {
+        // Once we loose track of the entity set there is no way to retrieve it.
+        break;
+      }
+      counter++;
+    }
+    return lastFoundEntitySetOrSingleton;
+  }
+
+  private EdmBindingTarget getEntitySetFromFunctionImport(UriResourceFunction uriResourceFunction) {
+    EdmFunctionImport functionImport = uriResourceFunction.getFunctionImport();
+    if (functionImport != null && functionImport.getReturnedEntitySet() != null
+        && !uriResourceFunction.isCollection()) {
+      return functionImport.getReturnedEntitySet();
+    }
+    return null;
+  }
+
+  private EdmBindingTarget getEntitySet(UriResourceEntitySet uriResourceEntitySet) {
+    if (!uriResourceEntitySet.isCollection()) {
+      return uriResourceEntitySet.getEntitySet();
+    }
+    return null;
+  }
+
+  private EdmBindingTarget getEntitySetFromNavigation(EdmBindingTarget lastFoundEntitySetOrSingleton,
+                                                      UriResourceNavigation uriResourceNavigation) {
+    if (lastFoundEntitySetOrSingleton != null && !uriResourceNavigation.isCollection()) {
+      EdmNavigationProperty navProp = uriResourceNavigation.getProperty();
+      return lastFoundEntitySetOrSingleton.getRelatedBindingTarget(navProp.getName());
+    }
+    return null;
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/test/java/org/apache/olingo/server/core/ETagParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/ETagParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/ETagParserTest.java
deleted file mode 100644
index 7931b2b..0000000
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/ETagParserTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.hasItems;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import org.apache.olingo.server.api.ETagInformation;
-import org.apache.olingo.server.api.OData;
-import org.junit.Test;
-
-public class ETagParserTest {
-
-  private static final OData odata = OData.newInstance();
-
-  @Test
-  public void empty() {
-    final ETagInformation eTagInformation = odata.createETagInformation(null);
-    assertFalse(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertTrue(eTagInformation.getETags().isEmpty());
-  }
-
-  @Test
-  public void loneStar() {
-    final ETagInformation eTagInformation = odata.createETagInformation(Collections.singleton("*"));
-    assertTrue(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertTrue(eTagInformation.getETags().isEmpty());
-  }
-
-  @Test
-  public void starWins() {
-    final ETagInformation eTagInformation = odata.createETagInformation(Arrays.asList("\"ETag\"", "*"));
-    assertTrue(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertTrue(eTagInformation.getETags().isEmpty());
-  }
-
-  @Test
-  public void starAsEtagAndEmptyEtag() {
-    final ETagInformation eTagInformation = odata.createETagInformation(
-        Collections.singleton("\"*\", \"\""));
-    assertFalse(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertThat(eTagInformation.getETags().size(), equalTo(2));
-    assertThat(eTagInformation.getETags(), hasItems("\"*\"", "\"\""));
-  }
-
-  @Test
-  public void severalEtags() {
-    final ETagInformation eTagInformation = odata.createETagInformation(
-        Arrays.asList("\"ETag1\"", "\"ETag2\",, , ,W/\"ETag3\", ,"));
-    assertFalse(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertThat(eTagInformation.getETags().size(), equalTo(3));
-    assertThat(eTagInformation.getETags(), hasItems("\"ETag1\"", "\"ETag2\"", "W/\"ETag3\""));
-  }
-
-  @Test
-  public void duplicateEtagValues() {
-    final ETagInformation eTagInformation = odata.createETagInformation(
-        Arrays.asList("\"ETag1\"", "\"ETag2\", W/\"ETag1\", \"ETag1\""));
-    assertFalse(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertThat(eTagInformation.getETags().size(), equalTo(3));
-    assertThat(eTagInformation.getETags(), hasItems("\"ETag1\"", "\"ETag2\"", "W/\"ETag1\""));
-  }
-
-  @Test
-  public void specialCharacters() {
-    final ETagInformation eTagInformation = odata.createETagInformation(
-        Collections.singleton("\"!#$%&'()*+,-./:;<=>?@[]^_`{|}~¡\u00FF\", \"ETag2\""));
-    assertFalse(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertThat(eTagInformation.getETags().size(), equalTo(2));
-    assertThat(eTagInformation.getETags(), hasItems(
-        "\"!#$%&'()*+,-./:;<=>?@[]^_`{|}~¡\u00FF\"", "\"ETag2\""));
-  }
-
-  @Test
-  public void wrongFormat() {
-    final ETagInformation eTagInformation = odata.createETagInformation(
-        Arrays.asList("\"ETag1\", ETag2", "w/\"ETag3\"", "W//\"ETag4\"", "W/ETag5",
-            "\"\"ETag6\"\"", " \"ETag7\"\"ETag7\" ", "\"ETag8\" \"ETag8\"",
-            "\"ETag 9\"", "\"ETag10\""));
-    assertFalse(eTagInformation.isAll());
-    assertNotNull(eTagInformation.getETags());
-    assertThat(eTagInformation.getETags().size(), equalTo(2));
-    assertThat(eTagInformation.getETags(), hasItems("\"ETag1\"", "\"ETag10\""));
-  }
-
-  @Test
-  public void match() {
-    assertFalse(odata.createETagInformation(Collections.<String> emptySet()).isMatchedBy("\"ETag\""));
-    assertFalse(odata.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy(null));
-    assertTrue(odata.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy("\"ETag\""));
-    assertTrue(odata.createETagInformation(Collections.singleton("*")).isMatchedBy("\"ETag\""));
-    assertTrue(odata.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy("W/\"ETag\""));
-    assertTrue(odata.createETagInformation(Collections.singleton("W/\"ETag\"")).isMatchedBy("\"ETag\""));
-    assertFalse(odata.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy("W/\"ETag2\""));
-    assertFalse(odata.createETagInformation(Collections.singleton("W/\"ETag\"")).isMatchedBy("\"ETag2\""));
-    assertTrue(odata.createETagInformation(Arrays.asList("\"ETag1\",\"ETag2\"", "\"ETag3\",\"ETag4\""))
-        .isMatchedBy("\"ETag4\""));
-    assertFalse(odata.createETagInformation(Arrays.asList("\"ETag1\",\"ETag2\"", "\"ETag3\",\"ETag4\""))
-        .isMatchedBy("\"ETag5\""));
-  }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-core/src/test/java/org/apache/olingo/server/core/etag/ETagParserTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/etag/ETagParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/etag/ETagParserTest.java
new file mode 100644
index 0000000..059d53d
--- /dev/null
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/etag/ETagParserTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.etag;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.junit.Test;
+
+public class ETagParserTest {
+
+  private static final ETagHelperImpl eTagHelper = new ETagHelperImpl();
+
+  @Test
+  public void empty() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(null);
+    assertFalse(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertTrue(eTagInformation.getETags().isEmpty());
+  }
+
+  @Test
+  public void loneStar() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(Collections.singleton("*"));
+    assertTrue(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertTrue(eTagInformation.getETags().isEmpty());
+  }
+
+  @Test
+  public void starWins() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(Arrays.asList("\"ETag\"", "*"));
+    assertTrue(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertTrue(eTagInformation.getETags().isEmpty());
+  }
+
+  @Test
+  public void starAsEtagAndEmptyEtag() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(
+        Collections.singleton("\"*\", \"\""));
+    assertFalse(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertThat(eTagInformation.getETags().size(), equalTo(2));
+    assertThat(eTagInformation.getETags(), hasItems("\"*\"", "\"\""));
+  }
+
+  @Test
+  public void severalEtags() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(
+        Arrays.asList("\"ETag1\"", "\"ETag2\",, , ,W/\"ETag3\", ,"));
+    assertFalse(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertThat(eTagInformation.getETags().size(), equalTo(3));
+    assertThat(eTagInformation.getETags(), hasItems("\"ETag1\"", "\"ETag2\"", "W/\"ETag3\""));
+  }
+
+  @Test
+  public void duplicateEtagValues() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(
+        Arrays.asList("\"ETag1\"", "\"ETag2\", W/\"ETag1\", \"ETag1\""));
+    assertFalse(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertThat(eTagInformation.getETags().size(), equalTo(3));
+    assertThat(eTagInformation.getETags(), hasItems("\"ETag1\"", "\"ETag2\"", "W/\"ETag1\""));
+  }
+
+  @Test
+  public void specialCharacters() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(
+        Collections.singleton("\"!#$%&'()*+,-./:;<=>?@[]^_`{|}~¡\u00FF\", \"ETag2\""));
+    assertFalse(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertThat(eTagInformation.getETags().size(), equalTo(2));
+    assertThat(eTagInformation.getETags(), hasItems(
+        "\"!#$%&'()*+,-./:;<=>?@[]^_`{|}~¡\u00FF\"", "\"ETag2\""));
+  }
+
+  @Test
+  public void wrongFormat() {
+    final ETagInformation eTagInformation = eTagHelper.createETagInformation(
+        Arrays.asList("\"ETag1\", ETag2", "w/\"ETag3\"", "W//\"ETag4\"", "W/ETag5",
+            "\"\"ETag6\"\"", " \"ETag7\"\"ETag7\" ", "\"ETag8\" \"ETag8\"",
+            "\"ETag 9\"", "\"ETag10\""));
+    assertFalse(eTagInformation.isAll());
+    assertNotNull(eTagInformation.getETags());
+    assertThat(eTagInformation.getETags().size(), equalTo(2));
+    assertThat(eTagInformation.getETags(), hasItems("\"ETag1\"", "\"ETag10\""));
+  }
+
+  @Test
+  public void match() {
+    assertFalse(eTagHelper.createETagInformation(Collections.<String> emptySet()).isMatchedBy("\"ETag\""));
+    assertFalse(eTagHelper.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy(null));
+    assertTrue(eTagHelper.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy("\"ETag\""));
+    assertTrue(eTagHelper.createETagInformation(Collections.singleton("*")).isMatchedBy("\"ETag\""));
+    assertTrue(eTagHelper.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy("W/\"ETag\""));
+    assertTrue(eTagHelper.createETagInformation(Collections.singleton("W/\"ETag\"")).isMatchedBy("\"ETag\""));
+    assertFalse(eTagHelper.createETagInformation(Collections.singleton("\"ETag\"")).isMatchedBy("W/\"ETag2\""));
+    assertFalse(eTagHelper.createETagInformation(Collections.singleton("W/\"ETag\"")).isMatchedBy("\"ETag2\""));
+    assertTrue(eTagHelper.createETagInformation(Arrays.asList("\"ETag1\",\"ETag2\"", "\"ETag3\",\"ETag4\""))
+        .isMatchedBy("\"ETag4\""));
+    assertFalse(eTagHelper.createETagInformation(Arrays.asList("\"ETag1\",\"ETag2\"", "\"ETag3\",\"ETag4\""))
+        .isMatchedBy("\"ETag5\""));
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/ETagSupport.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/ETagSupport.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/ETagSupport.java
index 9011107..09d4910 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/ETagSupport.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/ETagSupport.java
@@ -19,7 +19,7 @@
 package org.apache.olingo.server.tecsvc;
 
 import org.apache.olingo.commons.api.edm.EdmBindingTarget;
-import org.apache.olingo.server.api.CustomETagSupport;
+import org.apache.olingo.server.api.etag.CustomETagSupport;
 
 public class ETagSupport implements CustomETagSupport {
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java
index eaec6d0..086f69c 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalEntityProcessor.java
@@ -161,9 +161,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
           .validate(edmEntitySet, deserializerResult.getEntity());
 
       entity = dataProvider.create(edmEntitySet);
-      dataProvider.update(request.getRawBaseUri(), edmEntitySet, entity, deserializerResult.getEntity(), false,
-
-          true);
+      dataProvider.update(request.getRawBaseUri(), edmEntitySet, entity, deserializerResult.getEntity(), false, true);
       expand = deserializerResult.getExpandTree();
     }
 
@@ -199,7 +197,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
       }
     }
 
-    checkChangePreconditions(entity.getETag(),
+    odata.createETagHelper().checkChangePreconditions(entity.getETag(),
         request.getHeaders(HttpHeader.IF_MATCH),
         request.getHeaders(HttpHeader.IF_NONE_MATCH));
     checkRequestFormat(requestFormat);
@@ -232,7 +230,7 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
     final EdmEntityType edmEntityType = edmEntitySet.getEntityType();
 
     Entity entity = readEntity(uriInfo);
-    checkChangePreconditions(entity.getMediaETag(),
+    odata.createETagHelper().checkChangePreconditions(entity.getMediaETag(),
         request.getHeaders(HttpHeader.IF_MATCH),
         request.getHeaders(HttpHeader.IF_NONE_MATCH));
     checkRequestFormat(requestFormat);
@@ -251,13 +249,13 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
 
   @Override
   public void deleteEntity(final ODataRequest request, ODataResponse response, final UriInfo uriInfo)
-      throws ODataApplicationException {
+      throws ODataLibraryException, ODataApplicationException {
     final EdmEntitySet edmEntitySet = getEdmEntitySet(uriInfo);
     final Entity entity = readEntity(uriInfo);
     final List<UriResource> resourcePaths = uriInfo.getUriResourceParts();
     final boolean isValue = resourcePaths.get(resourcePaths.size() - 1) instanceof UriResourceValue;
 
-    checkChangePreconditions(isValue ? entity.getMediaETag() : entity.getETag(),
+    odata.createETagHelper().checkChangePreconditions(isValue ? entity.getMediaETag() : entity.getETag(),
         request.getHeaders(HttpHeader.IF_MATCH),
         request.getHeaders(HttpHeader.IF_NONE_MATCH));
     dataProvider.delete(edmEntitySet, entity);
@@ -353,9 +351,13 @@ public class TechnicalEntityProcessor extends TechnicalProcessor
 
     final Entity entity = readEntity(uriInfo);
 
-    checkReadPreconditions(entity.getETag(),
+    if (odata.createETagHelper().checkReadPreconditions(entity.getETag(),
         request.getHeaders(HttpHeader.IF_MATCH),
-        request.getHeaders(HttpHeader.IF_NONE_MATCH));
+        request.getHeaders(HttpHeader.IF_NONE_MATCH))) {
+      response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode());
+      response.setHeader(HttpHeader.ETAG, entity.getETag());
+      return;
+    }
 
     final ODataFormat format = ODataFormat.fromContentType(requestedContentType);
     final ExpandOption expand = uriInfo.getExpandOption();

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java
index 4ba7081..58b38c4 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalPrimitiveComplexProcessor.java
@@ -171,9 +171,13 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor
     final Entity entity = readEntity(uriInfo);
 
     if (entity != null && entity.getETag() != null) {
-      checkReadPreconditions(entity.getETag(),
+      if (odata.createETagHelper().checkReadPreconditions(entity.getETag(),
           request.getHeaders(HttpHeader.IF_MATCH),
-          request.getHeaders(HttpHeader.IF_NONE_MATCH));
+          request.getHeaders(HttpHeader.IF_NONE_MATCH))) {
+        response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode());
+        response.setHeader(HttpHeader.ETAG, entity.getETag());
+        return;
+      }
     }
 
     final Property property = entity == null ?
@@ -219,7 +223,7 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor
     final EdmEntitySet edmEntitySet = getEdmEntitySet(resource);
 
     Entity entity = readEntity(uriInfo);
-    checkChangePreconditions(entity.getETag(),
+    odata.createETagHelper().checkChangePreconditions(entity.getETag(),
         request.getHeaders(HttpHeader.IF_MATCH),
         request.getHeaders(HttpHeader.IF_NONE_MATCH));
 
@@ -252,13 +256,13 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor
   }
 
   private void deleteProperty(final ODataRequest request, ODataResponse response, final UriInfo uriInfo)
-      throws ODataApplicationException {
+      throws ODataLibraryException, ODataApplicationException {
     final UriInfoResource resource = uriInfo.asUriInfoResource();
     validatePath(resource);
     getEdmEntitySet(uriInfo); // including checks
 
     Entity entity = readEntity(uriInfo);
-    checkChangePreconditions(entity.getETag(),
+    odata.createETagHelper().checkChangePreconditions(entity.getETag(),
         request.getHeaders(HttpHeader.IF_MATCH),
         request.getHeaders(HttpHeader.IF_NONE_MATCH));
 
@@ -397,9 +401,13 @@ public class TechnicalPrimitiveComplexProcessor extends TechnicalProcessor
 
     final Entity entity = readEntity(uriInfo);
     if (entity != null && entity.getETag() != null) {
-      checkReadPreconditions(entity.getETag(),
+      if (odata.createETagHelper().checkReadPreconditions(entity.getETag(),
           request.getHeaders(HttpHeader.IF_MATCH),
-          request.getHeaders(HttpHeader.IF_NONE_MATCH));
+          request.getHeaders(HttpHeader.IF_NONE_MATCH))) {
+        response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode());
+        response.setHeader(HttpHeader.ETAG, entity.getETag());
+        return;
+      }
     }
 
     final Property property = entity == null ?

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
----------------------------------------------------------------------
diff --git a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
index b9e32fe..c95091d 100644
--- a/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
+++ b/lib/server-tecsvc/src/main/java/org/apache/olingo/server/tecsvc/processor/TechnicalProcessor.java
@@ -18,7 +18,6 @@
  */
 package org.apache.olingo.server.tecsvc.processor;
 
-import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 
@@ -32,7 +31,6 @@ import org.apache.olingo.commons.api.edm.EdmFunction;
 import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
 import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.commons.api.http.HttpStatusCode;
-import org.apache.olingo.server.api.ETagInformation;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataApplicationException;
 import org.apache.olingo.server.api.ServiceMetadata;
@@ -257,34 +255,4 @@ public abstract class TechnicalProcessor implements Processor {
           HttpStatusCode.BAD_REQUEST.getStatusCode(), Locale.ROOT);
     }
   }
-
-  protected void checkReadPreconditions(final String eTag,
-      final Collection<String> ifMatchHeaders, final Collection<String> ifNoneMatchHeaders)
-          throws ODataApplicationException {
-    if (eTag != null) {
-      final ETagInformation ifMatch = odata.createETagInformation(ifMatchHeaders);
-      if (!ifMatch.isMatchedBy(eTag) && !ifMatch.getETags().isEmpty()) {
-        throw new ODataApplicationException("The If-Match precondition is not fulfilled.",
-            HttpStatusCode.PRECONDITION_FAILED.getStatusCode(), Locale.ROOT);
-      }
-      if (odata.createETagInformation(ifNoneMatchHeaders).isMatchedBy(eTag)) {
-        throw new ODataApplicationException("The entity has not been modified.",
-            HttpStatusCode.NOT_MODIFIED.getStatusCode(), Locale.ROOT);
-      }
-    }
-  }
-
-  protected void checkChangePreconditions(final String eTag,
-      final Collection<String> ifMatchHeaders, final Collection<String> ifNoneMatchHeaders)
-          throws ODataApplicationException {
-    if (eTag != null) {
-      final ETagInformation ifMatch = odata.createETagInformation(ifMatchHeaders);
-      final ETagInformation ifNoneMatch = odata.createETagInformation(ifNoneMatchHeaders);
-      if (!ifMatch.isMatchedBy(eTag) && !ifMatch.getETags().isEmpty()
-          || ifNoneMatch.isMatchedBy(eTag)) {
-        throw new ODataApplicationException("The preconditions are not fulfilled.",
-            HttpStatusCode.PRECONDITION_FAILED.getStatusCode(), Locale.ROOT);
-      }
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/1f286fd4/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java
----------------------------------------------------------------------
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java
index 033f9af..7cab567 100644
--- a/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/PreconditionsValidatorTest.java
@@ -25,8 +25,10 @@ import org.apache.olingo.commons.api.edm.Edm;
 import org.apache.olingo.commons.api.edm.EdmBindingTarget;
 import org.apache.olingo.commons.api.http.HttpMethod;
 import org.apache.olingo.commons.core.edm.EdmProviderImpl;
-import org.apache.olingo.server.api.CustomETagSupport;
+import org.apache.olingo.server.api.etag.CustomETagSupport;
+import org.apache.olingo.server.api.etag.PreconditionRequiredException;
 import org.apache.olingo.server.api.uri.UriInfo;
+import org.apache.olingo.server.core.etag.PreconditionsValidator;
 import org.apache.olingo.server.core.uri.parser.Parser;
 import org.apache.olingo.server.core.uri.parser.UriParserException;
 import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;


Mime
View raw message