[OLINGO-995] Support HEAD for metadata and service document
diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java
index bb13989..55a04f5 100644
--- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java
+++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/http/BasicHttpITCase.java
@@ -19,6 +19,7 @@
 package org.apache.olingo.fit.tecsvc.http;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import java.net.HttpURLConnection;
@@ -39,6 +40,36 @@
   private static final String SERVICE_URI = TecSvcConst.BASE_URI + "/";
 
   @Test
+  public void testHeadMethodOnServiceDocument() throws Exception {
+    URL url = new URL(SERVICE_URI);
+
+    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+    connection.setRequestMethod(HttpMethod.HEAD.name());
+    connection.setRequestProperty(HttpHeader.ACCEPT, "application/json");
+    connection.connect();
+
+    assertEquals(HttpStatusCode.OK.getStatusCode(), connection.getResponseCode());
+    assertNull(connection.getHeaderField(HttpHeader.CONTENT_TYPE));
+    assertEquals("", IOUtils.toString(connection.getInputStream()));
+    connection.disconnect();
+  }
+  
+  @Test
+  public void testHeadMethodOnMetadataDocument() throws Exception {
+    URL url = new URL(SERVICE_URI + "$metadata");
+
+    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+    connection.setRequestMethod(HttpMethod.HEAD.name());
+    connection.setRequestProperty(HttpHeader.ACCEPT, "application/xml");
+    connection.connect();
+
+    assertEquals(HttpStatusCode.OK.getStatusCode(), connection.getResponseCode());
+    assertNull(connection.getHeaderField(HttpHeader.CONTENT_TYPE));
+    assertEquals("", IOUtils.toString(connection.getInputStream()));
+    connection.disconnect();
+  }
+
+  @Test
   public void testFormat() throws Exception {
     URL url = new URL(SERVICE_URI + "?$format=json");
 
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpMethod.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpMethod.java
index bccefd2..c77977a 100644
--- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpMethod.java
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpMethod.java
@@ -28,6 +28,7 @@
   PUT,
   PATCH,
   MERGE,
-  DELETE
+  DELETE,
+  HEAD
 
 }
diff --git a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpStatusCode.java b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpStatusCode.java
index 23bd85b..e1e4e32 100644
--- a/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpStatusCode.java
+++ b/lib/commons-api/src/main/java/org/apache/olingo/commons/api/http/HttpStatusCode.java
@@ -23,6 +23,10 @@
  * and additional status codes as defined in RFC 6585
  */
 public enum HttpStatusCode {
+  
+  CONTINUE(100, "Continue"),
+  SWITCHING_PROTOCOLS(101, "Switching Protocols"),
+  
   OK(200, "OK"),
   CREATED(201, "Created"),
   ACCEPTED(202, "Accepted"),
diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
index bde6aa4..83691b5 100644
--- a/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
+++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/processor/DefaultProcessor.java
@@ -23,6 +23,7 @@
 
 import org.apache.olingo.commons.api.format.ContentType;
 import org.apache.olingo.commons.api.http.HttpHeader;
+import org.apache.olingo.commons.api.http.HttpMethod;
 import org.apache.olingo.commons.api.http.HttpStatusCode;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.ODataApplicationException;
@@ -72,10 +73,15 @@
     if (isNotModified) {
       response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode());
     } else {
-      ODataSerializer serializer = odata.createSerializer(requestedContentType);
-      response.setContent(serializer.serviceDocument(serviceMetadata, null).getContent());
-      response.setStatusCode(HttpStatusCode.OK.getStatusCode());
-      response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
+      // HTTP HEAD requires no payload but a 200 OK response
+      if (HttpMethod.HEAD == request.getMethod()) {
+        response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+      } else {
+        ODataSerializer serializer = odata.createSerializer(requestedContentType);
+        response.setContent(serializer.serviceDocument(serviceMetadata, null).getContent());
+        response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+        response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
+      }
     }
   }
 
@@ -97,10 +103,15 @@
     if (isNotModified) {
       response.setStatusCode(HttpStatusCode.NOT_MODIFIED.getStatusCode());
     } else {
-      ODataSerializer serializer = odata.createSerializer(requestedContentType);
-      response.setContent(serializer.metadataDocument(serviceMetadata).getContent());
-      response.setStatusCode(HttpStatusCode.OK.getStatusCode());
-      response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
+      // HTTP HEAD requires no payload but a 200 OK response
+      if (HttpMethod.HEAD == request.getMethod()) {
+        response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+      } else {
+        ODataSerializer serializer = odata.createSerializer(requestedContentType);
+        response.setContent(serializer.metadataDocument(serviceMetadata).getContent());
+        response.setStatusCode(HttpStatusCode.OK.getStatusCode());
+        response.setHeader(HttpHeader.CONTENT_TYPE, requestedContentType.toContentTypeString());
+      }
     }
   }
 
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 3d4bdc7..138f45a 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
@@ -86,7 +86,7 @@
       ODataLibraryException {
     switch (uriInfo.getKind()) {
     case metadata:
-      checkMethod(request.getMethod(), HttpMethod.GET);
+      checkMethods(request.getMethod(), HttpMethod.GET, HttpMethod.HEAD);
       final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
           request, handler.getCustomContentTypeSupport(), RepresentationType.METADATA);
       handler.selectProcessor(MetadataProcessor.class)
@@ -94,7 +94,7 @@
       break;
 
     case service:
-      checkMethod(request.getMethod(), HttpMethod.GET);
+      checkMethods(request.getMethod(), HttpMethod.GET, HttpMethod.HEAD);
       if ("".equals(request.getRawODataPath())) {
         handler.selectProcessor(RedirectProcessor.class)
             .redirect(request, response);
@@ -267,7 +267,7 @@
     final HttpMethod httpMethod = request.getMethod();
     final boolean isCollection = ((UriResourcePartTyped) uriInfo.getUriResourceParts()
         .get(lastPathSegmentIndex - 1))
-        .isCollection();
+            .isCollection();
 
     if (isCollection && httpMethod == HttpMethod.GET) {
       final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
@@ -303,57 +303,57 @@
 
   private void handleValueDispatching(final ODataRequest request, final ODataResponse response,
       final int lastPathSegmentIndex) throws ODataApplicationException, ODataLibraryException {
-    //The URI Parser already checked if $value is allowed here so we only have to dispatch to the correct processor
+    // The URI Parser already checked if $value is allowed here so we only have to dispatch to the correct processor
     final HttpMethod method = request.getMethod();
     final UriResource resource = uriInfo.getUriResourceParts().get(lastPathSegmentIndex - 1);
     if (resource instanceof UriResourceProperty
         || resource instanceof UriResourceFunction
-        && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.PRIMITIVE) {
-      final EdmType type = resource instanceof UriResourceProperty ?
-          ((UriResourceProperty) resource).getType() : ((UriResourceFunction) resource).getType();
-          final RepresentationType valueRepresentationType =
-              type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Binary) ?
-                  RepresentationType.BINARY : RepresentationType.VALUE;
-          if (method == HttpMethod.GET) {
-            final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
-                request, handler.getCustomContentTypeSupport(), valueRepresentationType);
+            && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.PRIMITIVE) {
+      final EdmType type = resource instanceof UriResourceProperty ? ((UriResourceProperty) resource).getType()
+          : ((UriResourceFunction) resource).getType();
+      final RepresentationType valueRepresentationType =
+          type == EdmPrimitiveTypeFactory.getInstance(EdmPrimitiveTypeKind.Binary) ? RepresentationType.BINARY
+              : RepresentationType.VALUE;
+      if (method == HttpMethod.GET) {
+        final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
+            request, handler.getCustomContentTypeSupport(), valueRepresentationType);
 
-            handler.selectProcessor(PrimitiveValueProcessor.class)
+        handler.selectProcessor(PrimitiveValueProcessor.class)
             .readPrimitiveValue(request, response, uriInfo, requestedContentType);
-          } else if (method == HttpMethod.PUT && resource instanceof UriResourceProperty) {
-            validatePreconditions(request, false);
-            final ContentType requestFormat = getSupportedContentType(request.getHeader(HttpHeader.CONTENT_TYPE),
-                valueRepresentationType, true);
-            final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
-                request, handler.getCustomContentTypeSupport(), valueRepresentationType);
-            handler.selectProcessor(PrimitiveValueProcessor.class)
+      } else if (method == HttpMethod.PUT && resource instanceof UriResourceProperty) {
+        validatePreconditions(request, false);
+        final ContentType requestFormat = getSupportedContentType(request.getHeader(HttpHeader.CONTENT_TYPE),
+            valueRepresentationType, true);
+        final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
+            request, handler.getCustomContentTypeSupport(), valueRepresentationType);
+        handler.selectProcessor(PrimitiveValueProcessor.class)
             .updatePrimitiveValue(request, response, uriInfo, requestFormat, responseFormat);
-          } else if (method == HttpMethod.DELETE && resource instanceof UriResourceProperty) {
-            validatePreconditions(request, false);
-            handler.selectProcessor(PrimitiveValueProcessor.class)
+      } else if (method == HttpMethod.DELETE && resource instanceof UriResourceProperty) {
+        validatePreconditions(request, false);
+        handler.selectProcessor(PrimitiveValueProcessor.class)
             .deletePrimitiveValue(request, response, uriInfo);
-          } else {
-            throwMethodNotAllowed(method);
-          }
+      } else {
+        throwMethodNotAllowed(method);
+      }
     } else {
       if (method == HttpMethod.GET) {
-        //This can be a GET on an EntitySet, Navigation or Function
+        // This can be a GET on an EntitySet, Navigation or Function
         final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
             request, handler.getCustomContentTypeSupport(), RepresentationType.MEDIA);
         handler.selectProcessor(MediaEntityProcessor.class)
-        .readMediaEntity(request, response, uriInfo, requestedContentType);
-        //PUT and DELETE can only be called on EntitySets or Navigation properties which are media resources
+            .readMediaEntity(request, response, uriInfo, requestedContentType);
+        // PUT and DELETE can only be called on EntitySets or Navigation properties which are media resources
       } else if (method == HttpMethod.PUT && isEntityOrNavigationMedia(resource)) {
         validatePreconditions(request, true);
         final ContentType requestFormat = ContentType.parse(request.getHeader(HttpHeader.CONTENT_TYPE));
         final ContentType responseFormat = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
             request, handler.getCustomContentTypeSupport(), RepresentationType.ENTITY);
         handler.selectProcessor(MediaEntityProcessor.class)
-        .updateMediaEntity(request, response, uriInfo, requestFormat, responseFormat);
+            .updateMediaEntity(request, response, uriInfo, requestFormat, responseFormat);
       } else if (method == HttpMethod.DELETE && isEntityOrNavigationMedia(resource)) {
         validatePreconditions(request, true);
         handler.selectProcessor(MediaEntityProcessor.class)
-        .deleteMediaEntity(request, response, uriInfo);
+            .deleteMediaEntity(request, response, uriInfo);
       } else {
         throwMethodNotAllowed(method);
       }
@@ -363,8 +363,8 @@
   private void handleComplexDispatching(final ODataRequest request, final ODataResponse response,
       final boolean isCollection) throws ODataApplicationException, ODataLibraryException {
     final HttpMethod method = request.getMethod();
-    final RepresentationType complexRepresentationType = isCollection ?
-        RepresentationType.COLLECTION_COMPLEX : RepresentationType.COMPLEX;
+    final RepresentationType complexRepresentationType = isCollection ? RepresentationType.COLLECTION_COMPLEX
+        : RepresentationType.COMPLEX;
     if (method == HttpMethod.GET) {
       final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
           request, handler.getCustomContentTypeSupport(), complexRepresentationType);
@@ -405,8 +405,8 @@
   private void handlePrimitiveDispatching(final ODataRequest request, final ODataResponse response,
       final boolean isCollection) throws ODataApplicationException, ODataLibraryException {
     final HttpMethod method = request.getMethod();
-    final RepresentationType representationType = isCollection ?
-        RepresentationType.COLLECTION_PRIMITIVE : RepresentationType.PRIMITIVE;
+    final RepresentationType representationType = isCollection ? RepresentationType.COLLECTION_PRIMITIVE
+        : RepresentationType.PRIMITIVE;
     if (method == HttpMethod.GET) {
       final ContentType requestedContentType = ContentNegotiator.doContentNegotiation(uriInfo.getFormatOption(),
           request, handler.getCustomContentTypeSupport(), representationType);
@@ -450,12 +450,12 @@
     if (resource instanceof UriResourceEntitySet
         || resource instanceof UriResourceNavigation
         || resource instanceof UriResourceFunction
-        && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.ENTITY) {
+            && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.ENTITY) {
       handler.selectProcessor(CountEntityCollectionProcessor.class)
           .countEntityCollection(request, response, uriInfo);
     } else if (resource instanceof UriResourcePrimitiveProperty
         || resource instanceof UriResourceFunction
-        && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.PRIMITIVE) {
+            && ((UriResourceFunction) resource).getType().getKind() == EdmTypeKind.PRIMITIVE) {
       handler.selectProcessor(CountPrimitiveCollectionProcessor.class)
           .countPrimitiveCollection(request, response, uriInfo);
     } else {
@@ -533,6 +533,18 @@
     }
   }
 
+  private void checkMethods(final HttpMethod requestMethod, final HttpMethod... allowedMethods)
+      throws ODataHandlerException {
+    //Check if the request method is one of the allowed ones
+    for (int i = 0; i < allowedMethods.length; i++) {
+      if (requestMethod == allowedMethods[i]) {
+        return;
+      }
+    }
+    //request method does not match any allowed method
+    throwMethodNotAllowed(requestMethod);
+  }
+
   private void throwMethodNotAllowed(final HttpMethod httpMethod) throws ODataHandlerException {
     throw new ODataHandlerException("HTTP method " + httpMethod + " is not allowed.",
         ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, httpMethod.toString());
@@ -560,11 +572,11 @@
   }
 
   private boolean isEntityOrNavigationMedia(final UriResource pathSegment) {
-    //This method MUST NOT check if the resource is of type function since these are handled differently
+    // This method MUST NOT check if the resource is of type function since these are handled differently
     return pathSegment instanceof UriResourceEntitySet
         && ((UriResourceEntitySet) pathSegment).getEntityType().hasStream()
         || pathSegment instanceof UriResourceNavigation
-        && ((EdmEntityType) ((UriResourceNavigation) pathSegment).getType()).hasStream();
+            && ((EdmEntityType) ((UriResourceNavigation) pathSegment).getType()).hasStream();
   }
 
 }
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 f409903..20c0634 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
@@ -92,7 +92,8 @@
         || ODataHandlerException.MessageKeys.INVALID_HTTP_METHOD.equals(e.getMessageKey())
         || ODataHandlerException.MessageKeys.AMBIGUOUS_XHTTP_METHOD.equals(e.getMessageKey())
         || ODataHandlerException.MessageKeys.MISSING_CONTENT_TYPE.equals(e.getMessageKey())
-        || ODataHandlerException.MessageKeys.INVALID_CONTENT_TYPE.equals(e.getMessageKey())) {
+        || ODataHandlerException.MessageKeys.INVALID_CONTENT_TYPE.equals(e.getMessageKey())
+        || ODataHandlerException.MessageKeys.UNSUPPORTED_CONTENT_TYPE.equals(e.getMessageKey())) {
       serverError.setStatusCode(HttpStatusCode.BAD_REQUEST.getStatusCode());
     } else if (ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED.equals(e.getMessageKey())) {
       serverError.setStatusCode(HttpStatusCode.METHOD_NOT_ALLOWED.getStatusCode());
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 8fd533b..b6ebc6b 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
@@ -157,9 +157,9 @@
       }
     }
 
-    if (odResponse.getContent() != null ) {
+    if (odResponse.getContent() != null) {
       copyContent(odResponse.getContent(), response);
-    } else if(odResponse.getODataContent() != null) {
+    } else if (odResponse.getODataContent() != null) {
       writeContent(odResponse, response);
     }
   }
@@ -229,9 +229,14 @@
   }
 
   static HttpMethod extractMethod(final HttpServletRequest httpRequest) throws ODataLibraryException {
+    final HttpMethod httpRequestMethod;
     try {
-      HttpMethod httpRequestMethod = HttpMethod.valueOf(httpRequest.getMethod());
-
+      httpRequestMethod = HttpMethod.valueOf(httpRequest.getMethod());
+    } catch (IllegalArgumentException e) {
+      throw new ODataHandlerException("HTTP method not allowed" + httpRequest.getMethod(), e,
+          ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED, httpRequest.getMethod());
+    }
+    try {
       if (httpRequestMethod == HttpMethod.POST) {
         String xHttpMethod = httpRequest.getHeader(HttpHeader.X_HTTP_METHOD);
         String xHttpMethodOverride = httpRequest.getHeader(HttpHeader.X_HTTP_METHOD_OVERRIDE);
@@ -301,7 +306,7 @@
   static void copyHeaders(ODataRequest odRequest, final HttpServletRequest req) {
     for (final Enumeration<?> headerNames = req.getHeaderNames(); headerNames.hasMoreElements();) {
       final String headerName = (String) headerNames.nextElement();
-      @SuppressWarnings("unchecked") 
+      @SuppressWarnings("unchecked")
       // getHeaders() says it returns an Enumeration of String.
       final List<String> headerValues = Collections.list(req.getHeaders(headerName));
       odRequest.addHeader(headerName, headerValues);
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/ExceptionHelperTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/ExceptionHelperTest.java
index c4eb24c..dffbf82 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/ExceptionHelperTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/ExceptionHelperTest.java
@@ -106,9 +106,26 @@
     }
   }
 
+  @Test
+  public void httpHandlerExceptions() {
+    for (MessageKey key : ODataHandlerException.MessageKeys.values()) {
+      final ODataHandlerException e = new ODataHandlerException(DEV_MSG, key);
+      ODataServerError serverError = ODataExceptionHelper.createServerErrorObject(e, null);
+
+      if (key.equals(ODataHandlerException.MessageKeys.FUNCTIONALITY_NOT_IMPLEMENTED)
+          || key.equals(ODataHandlerException.MessageKeys.PROCESSOR_NOT_IMPLEMENTED)) {
+        checkStatusCode(serverError, HttpStatusCode.NOT_IMPLEMENTED, e);
+      } else if (key.equals(ODataHandlerException.MessageKeys.HTTP_METHOD_NOT_ALLOWED)) {
+        checkStatusCode(serverError, HttpStatusCode.METHOD_NOT_ALLOWED, e);
+      } else {
+        checkStatusCode(serverError, HttpStatusCode.BAD_REQUEST, e);
+      }
+    }
+  }
+
   private void checkStatusCode(final ODataServerError serverError, final HttpStatusCode statusCode,
       final ODataLibraryException exception) {
     assertEquals("FailedKey: " + exception.getMessageKey().getKey(),
-        serverError.getStatusCode(), statusCode.getStatusCode());
+        statusCode.getStatusCode(), serverError.getStatusCode());
   }
 }
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java
index 1a66609..2a58f57 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/ODataHttpHandlerImplTest.java
@@ -49,6 +49,7 @@
         { "POST", "PATCH", null, "PATCH" },
 
         { "POST", "GET", "GET", "GET" },
+        { "HEAD", null, null, "HEAD" }
     };
 
     for (String[] m : mm) {
@@ -68,8 +69,7 @@
     String[][] mm = {
         { "POST", "bla", null },
         { "POST", "PUT", "PATCH" },
-        { "OPTIONS", null, null },
-        { "HEAD", null, null },
+        { "OPTIONS", null, null }
     };
 
     for (String[] m : mm) {
diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerImplTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerImplTest.java
index 03a513e..591e8e2 100644
--- a/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerImplTest.java
+++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/ODataHandlerImplTest.java
@@ -18,11 +18,10 @@
  */
 package org.apache.olingo.server.core;
 
-import java.io.InputStream;
-import java.nio.charset.Charset;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.doThrow;
@@ -31,6 +30,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import java.io.InputStream;
+import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.Locale;
 
@@ -90,12 +91,26 @@
   @Test
   public void serviceDocumentNonDefault() throws Exception {
     final ServiceDocumentProcessor processor = mock(ServiceDocumentProcessor.class);
+    doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor)
+        .readServiceDocument(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+            any(ContentType.class));
     final ODataResponse response = dispatch(HttpMethod.GET, "/", processor);
-    assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatusCode());
+    assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response.getStatusCode());
 
     verify(processor).readServiceDocument(
         any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
 
+    // We support HEAD now too
+    final ServiceDocumentProcessor processor2 = mock(ServiceDocumentProcessor.class);
+    doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor2)
+    .readServiceDocument(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+        any(ContentType.class));
+    final ODataResponse response2 = dispatch(HttpMethod.HEAD, "/", processor2);
+    assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response2.getStatusCode());
+
+    verify(processor2).readServiceDocument(
+        any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+
     dispatchMethodNotAllowed(HttpMethod.POST, "/", processor);
     dispatchMethodNotAllowed(HttpMethod.PATCH, "/", processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, "/", processor);
@@ -116,6 +131,11 @@
 
     assertThat(doc, containsString("\"@odata.context\":\"$metadata\""));
     assertThat(doc, containsString("\"value\":"));
+    
+    final ODataResponse response2 = dispatch(HttpMethod.HEAD, "/", null);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), response2.getStatusCode());
+    assertNull(response2.getHeader(HttpHeader.CONTENT_TYPE));
+    assertNull(response2.getContent());
   }
 
   @Test
@@ -123,16 +143,34 @@
     final ODataResponse response = dispatch(HttpMethod.GET, "", null);
     assertEquals(HttpStatusCode.TEMPORARY_REDIRECT.getStatusCode(), response.getStatusCode());
     assertEquals(BASE_URI + "/", response.getHeader(HttpHeader.LOCATION));
+    
+    final ODataResponse responseHead = dispatch(HttpMethod.HEAD, "", null);
+    assertEquals(HttpStatusCode.TEMPORARY_REDIRECT.getStatusCode(), responseHead.getStatusCode());
+    assertEquals(BASE_URI + "/", responseHead.getHeader(HttpHeader.LOCATION));
   }
 
   @Test
   public void metadataNonDefault() throws Exception {
     final MetadataProcessor processor = mock(MetadataProcessor.class);
+    doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor)
+    .readMetadata(
+        any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
     final ODataResponse response = dispatch(HttpMethod.GET, "$metadata", processor);
-    assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatusCode());
+    assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response.getStatusCode());
 
     verify(processor).readMetadata(
         any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
+    
+    // We support HEAD now too
+    final MetadataProcessor processor2 = mock(MetadataProcessor.class);
+    doThrow(new ODataApplicationException("msg", 100, Locale.ENGLISH)).when(processor2)
+    .readMetadata(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class),
+        any(ContentType.class));
+    final ODataResponse response2 = dispatch(HttpMethod.HEAD, "$metadata", processor2);
+    assertEquals(HttpStatusCode.CONTINUE.getStatusCode(), response2.getStatusCode());
+
+    verify(processor2).readMetadata(
+        any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, "$metadata", processor);
     dispatchMethodNotAllowed(HttpMethod.PATCH, "$metadata", processor);
@@ -149,6 +187,11 @@
     assertNotNull(response.getContent());
     assertThat(IOUtils.toString(response.getContent()),
         containsString("<edmx:Edmx Version=\"4.0\""));
+    
+    final ODataResponse response2 = dispatch(HttpMethod.HEAD, "$metadata", null);
+    assertEquals(HttpStatusCode.OK.getStatusCode(), response2.getStatusCode());
+    assertNull(response2.getHeader(HttpHeader.CONTENT_TYPE));
+    assertNull(response2.getContent());
   }
 
   @Test
@@ -209,7 +252,6 @@
     assertEquals(HttpStatusCode.BAD_REQUEST.getStatusCode(), response.getStatusCode());
   }
 
-
   @Test
   public void uriParserExceptionWithFormatQueryJson() throws Exception {
     final ODataResponse response = dispatch(HttpMethod.GET, "ESAllPrims", "$format=json", "", "", null);
@@ -226,7 +268,6 @@
         response.getHeader(HttpHeader.CONTENT_TYPE));
   }
 
-
   @Test
   public void uriParserExceptionWithFormatJsonAcceptAtom() throws Exception {
     final ODataResponse response = dispatch(HttpMethod.GET, "ESAllPrims", "$format=json",
@@ -268,27 +309,27 @@
     assertEquals("application/json;odata.metadata=minimal",
         response.getHeader(HttpHeader.CONTENT_TYPE));
   }
-  
+
   @Test
   public void applicationExceptionInProcessorMessage() throws Exception {
     final String ODATA_ERRORCODE = "425";
     final String ORIGINAL_MESSAGE = "original message";
     final String LOCALIZED_MESSAGE = "localized message";
     MetadataProcessor processor = mock(MetadataProcessor.class);
-    
-    ODataApplicationException oDataApplicationException = 
-        new ODataApplicationException(ORIGINAL_MESSAGE, 425, Locale.ENGLISH, ODATA_ERRORCODE) {
-      private static final long serialVersionUID = 1L;
 
-      @Override
-      public String getLocalizedMessage() {
-          return LOCALIZED_MESSAGE;
-      }
-    };
-    
+    ODataApplicationException oDataApplicationException =
+        new ODataApplicationException(ORIGINAL_MESSAGE, 425, Locale.ENGLISH, ODATA_ERRORCODE) {
+          private static final long serialVersionUID = 1L;
+
+          @Override
+          public String getLocalizedMessage() {
+            return LOCALIZED_MESSAGE;
+          }
+        };
+
     doThrow(oDataApplicationException).when(processor).readMetadata(
         any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
-    
+
     final ODataResponse response = dispatch(HttpMethod.GET, "$metadata", processor);
     InputStream contentStream = response.getContent();
     String responseContent = IOUtils.toString(contentStream, Charset.forName("UTF-8"));
@@ -299,7 +340,7 @@
     // test if the original is hold
     assertEquals(ORIGINAL_MESSAGE, oDataApplicationException.getMessage());
   }
-  
+
   @Test
   public void applicationExceptionInProcessor() throws Exception {
     MetadataProcessor processor = mock(MetadataProcessor.class);
@@ -345,6 +386,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -359,6 +401,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -374,15 +417,22 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
   public void dispatchCountWithNavigation() throws Exception {
     final CountEntityCollectionProcessor processor = mock(CountEntityCollectionProcessor.class);
-    dispatch(HttpMethod.GET, "ESAllPrim(0)/NavPropertyETTwoPrimMany/$count", processor);
+    String uri = "ESAllPrim(0)/NavPropertyETTwoPrimMany/$count";
+    dispatch(HttpMethod.GET, uri, processor);
 
     verify(processor).countEntityCollection(
         any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+    dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -406,6 +456,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, entityCountUri, entityCountProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, entityCountUri, entityCountProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, entityCountUri, entityCountProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, entityCountUri, entityCountProcessor);
 
     PrimitiveProcessor primitiveProcessor = mock(PrimitiveProcessor.class);
     dispatch(HttpMethod.GET, "FICRTString()", primitiveProcessor);
@@ -420,6 +471,7 @@
     dispatchMethodWithError(HttpMethod.PATCH, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
     dispatchMethodWithError(HttpMethod.PUT, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
     dispatchMethodWithError(HttpMethod.DELETE, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
+    dispatchMethodWithError(HttpMethod.HEAD, valueUri, primitiveValueProcessor, HttpStatusCode.BAD_REQUEST);
 
     final String primitiveCollectionUri = "FICRTCollString()";
     PrimitiveCollectionProcessor primitiveCollectionProcessor = mock(PrimitiveCollectionProcessor.class);
@@ -430,6 +482,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, primitiveCollectionUri, primitiveCollectionProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, primitiveCollectionUri, primitiveCollectionProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, primitiveCollectionUri, primitiveCollectionProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, primitiveCollectionUri, primitiveCollectionProcessor);
 
     final String primitiveCountUri = "FICRTCollString()/$count";
     final CountPrimitiveCollectionProcessor primitiveCountProcessor = mock(CountPrimitiveCollectionProcessor.class);
@@ -440,6 +493,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, primitiveCountUri, primitiveCountProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, primitiveCountUri, primitiveCountProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, primitiveCountUri, primitiveCountProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, primitiveCountUri, primitiveCountProcessor);
 
     ComplexProcessor complexProcessor = mock(ComplexProcessor.class);
     dispatch(HttpMethod.GET, "FICRTCTTwoPrim()", complexProcessor);
@@ -460,6 +514,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, complexCountUri, complexCountProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, complexCountUri, complexCountProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, complexCountUri, complexCountProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, complexCountUri, complexCountProcessor);
 
     final String mediaUri = "FICRTESMedia(ParameterInt16=1)/$value";
     final MediaEntityProcessor mediaProcessor = mock(MediaEntityProcessor.class);
@@ -470,6 +525,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, mediaUri, mediaProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, mediaUri, mediaProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, mediaUri, mediaProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, mediaUri, mediaProcessor);
   }
 
   @Test
@@ -483,6 +539,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, ContainerProvider.AIRT_STRING, primitiveProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, ContainerProvider.AIRT_STRING, primitiveProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, ContainerProvider.AIRT_STRING, primitiveProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, ContainerProvider.AIRT_STRING, primitiveProcessor);
 
     ActionPrimitiveCollectionProcessor primitiveCollectionProcessor = mock(ActionPrimitiveCollectionProcessor.class);
     dispatch(HttpMethod.POST, ContainerProvider.AIRT_COLL_STRING_TWO_PARAM, primitiveCollectionProcessor);
@@ -534,6 +591,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, ContainerProvider.AIRT, voidProcessor);
     dispatchMethodNotAllowed(HttpMethod.PUT, ContainerProvider.AIRT, voidProcessor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, ContainerProvider.AIRT, voidProcessor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, ContainerProvider.AIRT, voidProcessor);
   }
 
   @Test
@@ -563,6 +621,7 @@
         any(ContentType.class), any(ContentType.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -587,8 +646,9 @@
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
-  
+
   @Test
   public void dispatchValueOnNoMedia() throws Exception {
     final String uri = "ESAllPrim(1)/$value";
@@ -605,6 +665,9 @@
 
     dispatch(HttpMethod.DELETE, uri, processor);
     verifyZeroInteractions(processor);
+    
+    dispatch(HttpMethod.HEAD, uri, processor);
+    verifyZeroInteractions(processor);
   }
 
   @Test
@@ -621,7 +684,7 @@
         any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class), any(ContentType.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, "ESKeyNav(1)/NavPropertyETMediaOne", processor);
-    
+
     dispatchMethodNotAllowed(HttpMethod.POST, "ESKeyNav(1)/NavPropertyETMediaOne/$value", processor);
 
     dispatch(HttpMethod.PUT, uri, processor);
@@ -633,6 +696,7 @@
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -641,6 +705,7 @@
     dispatch(HttpMethod.DELETE, "ESMedia(1)", processor);
 
     verify(processor).deleteEntity(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+    dispatchMethodNotAllowed(HttpMethod.HEAD, "ESMedia(1)", processor);
   }
 
   @Test
@@ -666,6 +731,7 @@
     verify(processor).deletePrimitive(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -688,6 +754,7 @@
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -708,6 +775,7 @@
     verify(processor).deletePrimitiveCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -722,6 +790,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -747,6 +816,7 @@
     verify(processor).deleteComplex(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -767,6 +837,7 @@
     verify(processor).deleteComplexCollection(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
 
     dispatchMethodNotAllowed(HttpMethod.POST, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -781,6 +852,7 @@
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.DELETE, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -809,6 +881,8 @@
 
     dispatch(HttpMethod.DELETE, uriMany, "$id=ESTwoPrim(1)", null, null, processor);
     verify(processor).deleteReference(any(ODataRequest.class), any(ODataResponse.class), any(UriInfo.class));
+    
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test
@@ -822,6 +896,7 @@
 
     dispatchMethodNotAllowed(HttpMethod.PATCH, uri, processor);
     dispatchMethodNotAllowed(HttpMethod.PUT, uri, processor);
+    dispatchMethodNotAllowed(HttpMethod.HEAD, uri, processor);
   }
 
   @Test