You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by mi...@apache.org on 2014/03/16 10:06:09 UTC

[20/50] [abbrv] git commit: [OLINGO-155] debug output: restructured, HTML format added

[OLINGO-155] debug output: restructured, HTML format added

Change-Id: I5b5691a1e98654ad6663e6ef5f6ac03d4dac2720

Signed-off-by: Christian Amend <ch...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/commit/6b9a4e67
Tree: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/tree/6b9a4e67
Diff: http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/diff/6b9a4e67

Branch: refs/heads/Olingo-129_PocJpaDataStore
Commit: 6b9a4e678bd49958e975511df3ecbdd1b3e7c32a
Parents: a8461c7
Author: Klaus Straubinger <kl...@sap.com>
Authored: Tue Jan 21 13:24:56 2014 +0100
Committer: Christian Amend <ch...@apache.org>
Committed: Wed Feb 12 10:19:12 2014 +0100

----------------------------------------------------------------------
 .../odata2/api/commons/HttpContentType.java     |   2 +
 .../olingo/odata2/core/ODataContextImpl.java    |  13 +-
 .../olingo/odata2/core/ODataRequestHandler.java |   9 +-
 .../olingo/odata2/core/debug/DebugInfo.java     |   9 +-
 .../olingo/odata2/core/debug/DebugInfoBody.java | 111 +++++++---
 .../odata2/core/debug/DebugInfoException.java   |  56 ++++-
 .../odata2/core/debug/DebugInfoRequest.java     |  40 +++-
 .../odata2/core/debug/DebugInfoResponse.java    |  51 +++--
 .../odata2/core/debug/DebugInfoRuntime.java     |  63 +++++-
 .../odata2/core/debug/DebugInfoServer.java      |  74 +++++++
 .../olingo/odata2/core/debug/DebugInfoUri.java  | 212 ++++++++++++++-----
 .../core/debug/ODataDebugResponseWrapper.java   | 177 ++++++++++++++--
 .../odata2/core/debug/DebugInfoBodyTest.java    |   8 +-
 .../debug/ODataDebugResponseWrapperTest.java    | 187 ++++++++++------
 14 files changed, 792 insertions(+), 220 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/commons/HttpContentType.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/commons/HttpContentType.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/commons/HttpContentType.java
index 7f51ae3..5f5c6ec 100644
--- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/commons/HttpContentType.java
+++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/commons/HttpContentType.java
@@ -45,6 +45,8 @@ public interface HttpContentType {
   public static final String TEXT_PLAIN = "text/plain";
   public static final String TEXT_PLAIN_UTF8 = TEXT_PLAIN + ";charset=utf-8";
 
+  public static final String TEXT_HTML = "text/html";
+
   public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
 
   public static final String APPLICATION_HTTP = "application/http";

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataContextImpl.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataContextImpl.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataContextImpl.java
index 92ddd35..3a75f73 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataContextImpl.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataContextImpl.java
@@ -36,7 +36,7 @@ import org.apache.olingo.odata2.api.uri.PathInfo;
 import org.apache.olingo.odata2.core.debug.ODataDebugResponseWrapper;
 
 /**
- *  
+ * Context. 
  */
 public class ODataContextImpl implements ODataContext {
 
@@ -158,9 +158,9 @@ public class ODataContextImpl implements ODataContext {
     return null;
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public List<RuntimeMeasurement> getRuntimeMeasurements() {
+    @SuppressWarnings("unchecked")
     List<RuntimeMeasurement> runtimeMeasurements = (List<RuntimeMeasurement>) getParameter(RUNTIME_MEASUREMENTS);
     if (runtimeMeasurements == null) {
       runtimeMeasurements = new ArrayList<RuntimeMeasurement>();
@@ -219,8 +219,8 @@ public class ODataContextImpl implements ODataContext {
 
     @Override
     public String toString() {
-      return className + "." + methodName + ": duration: " + (timeStopped - timeStarted) + ", memory: "
-          + (memoryStopped - memoryStarted);
+      return className + "." + methodName + ": duration: " + (timeStopped - timeStarted)
+          + ", memory: " + (memoryStopped - memoryStarted);
     }
 
     @Override
@@ -290,8 +290,9 @@ public class ODataContextImpl implements ODataContext {
 
   private static String getQueryDebugValue(final Map<String, String> queryParameters) {
     final String debugValue = queryParameters.get(ODataDebugResponseWrapper.ODATA_DEBUG_QUERY_PARAMETER);
-    return ODataDebugResponseWrapper.ODATA_DEBUG_JSON.equals(debugValue) ?
-        debugValue : null;
+    return ODataDebugResponseWrapper.ODATA_DEBUG_JSON.equals(debugValue)
+        || ODataDebugResponseWrapper.ODATA_DEBUG_HTML.equals(debugValue)
+        || ODataDebugResponseWrapper.ODATA_DEBUG_DOWNLOAD.equals(debugValue) ? debugValue : null;
   }
 
   public void setBatchParentContext(final ODataContext ctx) {

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataRequestHandler.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataRequestHandler.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataRequestHandler.java
index a4a7b56..f244bf7 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataRequestHandler.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ODataRequestHandler.java
@@ -155,12 +155,13 @@ public class ODataRequestHandler {
     context.stopRuntimeMeasurement(timingHandle);
 
     if (context.isInDebugMode()) {
-      if (getQueryDebugValue(request.getQueryParameters()) == null) {
+      final String debugValue = getQueryDebugValue(request.getQueryParameters());
+      if (debugValue == null) {
         ODataDebugResponseWrapperCallback callback =
             context.getServiceFactory().getCallback(ODataDebugResponseWrapperCallback.class);
         return callback == null ? odataResponse : callback.handle(context, request, odataResponse, uriInfo, exception);
       } else {
-        return new ODataDebugResponseWrapper(context, odataResponse, uriInfo, exception).wrapResponse();
+        return new ODataDebugResponseWrapper(context, odataResponse, uriInfo, exception, debugValue).wrapResponse();
       }
     } else {
       return odataResponse;
@@ -526,6 +527,8 @@ public class ODataRequestHandler {
 
   private static String getQueryDebugValue(final Map<String, String> queryParameters) {
     final String debugValue = queryParameters.get(ODataDebugResponseWrapper.ODATA_DEBUG_QUERY_PARAMETER);
-    return ODataDebugResponseWrapper.ODATA_DEBUG_JSON.equals(debugValue) ? debugValue : null;
+    return ODataDebugResponseWrapper.ODATA_DEBUG_JSON.equals(debugValue)
+        || ODataDebugResponseWrapper.ODATA_DEBUG_HTML.equals(debugValue)
+        || ODataDebugResponseWrapper.ODATA_DEBUG_DOWNLOAD.equals(debugValue) ? debugValue : null;
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfo.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfo.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfo.java
index bb04303..3f5a0ff 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfo.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfo.java
@@ -19,11 +19,12 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
+import java.io.Writer;
 
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 
 /**
- *  
+ * Debug information.
  */
 public interface DebugInfo {
 
@@ -39,4 +40,10 @@ public interface DebugInfo {
    * @param jsonStreamWriter a JSON stream writer
    */
   public void appendJson(JsonStreamWriter jsonStreamWriter) throws IOException;
+
+  /**
+   * Appends the content of this debug information part to the given writer.
+   * @param writer a {@link Writer}
+   */
+  public void appendHtml(Writer writer) throws IOException;
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoBody.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoBody.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoBody.java
index 8b68337..b893e14 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoBody.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoBody.java
@@ -20,6 +20,17 @@ package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.regex.Pattern;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.olingo.odata2.api.commons.HttpContentType;
@@ -28,15 +39,31 @@ import org.apache.olingo.odata2.api.processor.ODataResponse;
 import org.apache.olingo.odata2.core.ep.BasicEntityProvider;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
+
 /**
- *  
+ * Response body debug information.
  */
 public class DebugInfoBody implements DebugInfo {
 
   private final ODataResponse response;
+  private final String serviceRoot;
+  private final boolean isXml;
+  private final boolean isJson;
+  private final boolean isText;
+  private final boolean isImage;
 
-  public DebugInfoBody(final ODataResponse response) {
+  public DebugInfoBody(final ODataResponse response, final String serviceRoot) {
     this.response = response;
+    this.serviceRoot = serviceRoot;
+    final String contentType = response.getContentHeader();
+    isXml = contentType.contains("xml");
+    isJson = !isXml && contentType.startsWith(HttpContentType.APPLICATION_JSON);
+    isText = isXml || isJson || contentType.startsWith("text/")
+        || contentType.startsWith(HttpContentType.APPLICATION_HTTP)
+        || contentType.startsWith(HttpContentType.MULTIPART_MIXED);
+    isImage = !isText && contentType.startsWith("image/");
   }
 
   @Override
@@ -45,49 +72,77 @@ public class DebugInfoBody implements DebugInfo {
   }
 
   @Override
-  public void appendJson(final JsonStreamWriter jsonStreamWriter) throws IOException {
-    final String contentType = response.getContentHeader();
-    if (contentType.startsWith("image/")) {
-      if (response.getEntity() instanceof InputStream) {
-        jsonStreamWriter.stringValueRaw(Base64.encodeBase64String(getBinaryFromInputStream((InputStream) response
-            .getEntity())));
-      } else if (response.getEntity() instanceof String) {
-        jsonStreamWriter.stringValueRaw(getContentString());
-      } else {
-        throw new ClassCastException("Unsupported content entity class: " + response.getEntity().getClass().getName());
-      }
-    } else if (contentType.startsWith(HttpContentType.APPLICATION_JSON)) {
+  public void appendJson(JsonStreamWriter jsonStreamWriter) throws IOException {
+    if (isJson) {
       jsonStreamWriter.unquotedValue(getContentString());
-    } else {
+    } else if (isText) {
       jsonStreamWriter.stringValue(getContentString());
+    } else {
+      jsonStreamWriter.stringValueRaw(getContentString());
     }
   }
 
   private String getContentString() {
-    String content;
     if (response.getEntity() instanceof String) {
-      content = (String) response.getEntity();
+      return (String) response.getEntity();
     } else if (response.getEntity() instanceof InputStream) {
-      content = getStringFromInputStream((InputStream) response.getEntity());
+      InputStream input = (InputStream) response.getEntity();
+      try {
+        return isText ?
+            new BasicEntityProvider().readText(input) :
+            Base64.encodeBase64String(new BasicEntityProvider().readBinary((input)));
+      } catch (final EntityProviderException e) {
+        return null;
+      }
     } else {
       throw new ClassCastException("Unsupported content entity class: " + response.getEntity().getClass().getName());
     }
-    return content;
   }
 
-  private static byte[] getBinaryFromInputStream(final InputStream inputStream) {
-    try {
-      return new BasicEntityProvider().readBinary(inputStream);
-    } catch (final EntityProviderException e) {
-      return null;
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    final String body = getContentString();
+    if (isImage) {
+      writer.append("<img src=\"data:").append(response.getContentHeader()).append(";base64,")
+          .append(body)
+          .append("\" />\n");
+    } else {
+      writer.append("<pre class=\"code").append(isXml ? " xml" : isJson ? " json" : "").append("\">\n")
+          .append(isXml || isJson ?
+              addLinks(ODataDebugResponseWrapper.escapeHtml(isXml ? formatXml(body) : formatJson(body)), isXml) :
+              ODataDebugResponseWrapper.escapeHtml(body))
+          .append("</pre>\n");
     }
   }
 
-  private static String getStringFromInputStream(final InputStream inputStream) {
+  private String formatXml(final String xml) throws IOException {
     try {
-      return new BasicEntityProvider().readText(inputStream);
-    } catch (final EntityProviderException e) {
-      return null;
+      Transformer transformer = TransformerFactory.newInstance().newTransformer();
+      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+      StreamResult outputTarget = new StreamResult(new StringWriter());
+      transformer.transform(new StreamSource(new StringReader(xml)), outputTarget);
+      return outputTarget.getWriter().toString();
+    } catch (final TransformerException e) {
+      return xml;
     }
   }
+
+  private String formatJson(final String json) {
+    return new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(new JsonParser().parse(json));
+  }
+
+  private String addLinks(final String source, final boolean isXml) {
+    final String debugOption = ODataDebugResponseWrapper.ODATA_DEBUG_QUERY_PARAMETER + "="
+        + ODataDebugResponseWrapper.ODATA_DEBUG_HTML;
+    final String urlPattern = "("
+        + (isXml ? "(?:href|src|base)=" : "\"(?:uri|media_src|edit_media|__next)\":\\p{Space}*")
+        + "\")(.+?)\"";
+    return (isXml ? source.replaceAll("(xmlns(?::\\p{Alnum}+)?=\")(.+?)\"", "$1<span class=\"ns\">$2</span>\"") :
+        source)
+        .replaceAll(urlPattern, "$1<a href=\"" + serviceRoot + "$2?" + debugOption + "\">$2</a>\"")
+        .replaceAll("(<a href=\"" + Pattern.quote(serviceRoot) + ')' + Pattern.quote(serviceRoot), "$1")
+        .replaceAll("<a href=\"(.+?)\\?(.+?)\\?" + debugOption, "<a href=\"$1?$2&amp;" + debugOption)
+        .replaceAll("&amp;amp;", "&amp;");
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoException.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoException.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoException.java
index c84f51f..4201ba1 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoException.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoException.java
@@ -19,6 +19,7 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.util.Locale;
 
 import org.apache.olingo.odata2.api.exception.ODataMessageException;
@@ -26,7 +27,7 @@ import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 import org.apache.olingo.odata2.core.exception.MessageService;
 
 /**
- *  
+ * Exception debug information.
  */
 public class DebugInfoException implements DebugInfo {
 
@@ -52,12 +53,7 @@ public class DebugInfoException implements DebugInfo {
     while (throwable != null) {
       jsonStreamWriter.beginObject()
           .namedStringValueRaw("class", throwable.getClass().getCanonicalName()).separator()
-          .namedStringValue(
-              "message",
-              throwable instanceof ODataMessageException ?
-                  MessageService.getMessage(locale, ((ODataMessageException) throwable).getMessageReference())
-                      .getText() :
-                  throwable.getLocalizedMessage())
+          .namedStringValue("message", getMessageText(throwable))
           .separator();
 
       jsonStreamWriter.name("invocation");
@@ -94,4 +90,50 @@ public class DebugInfoException implements DebugInfo {
         .name("line").unquotedValue(Integer.toString(stackTraceElement.getLineNumber()))
         .endObject();
   }
+
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    appendException(exception, writer);
+    writer.append("<h2>Stacktrace</h2>\n");
+    int count = 0;
+    for (final StackTraceElement stackTraceElement : exception.getStackTrace()) {
+      appendStackTraceElement(stackTraceElement, ++count == 1, count == exception.getStackTrace().length, writer);
+    }
+  }
+
+  private void appendException(final Throwable throwable, Writer writer) throws IOException {
+    if (throwable.getCause() != null) {
+      appendException(throwable.getCause(), writer);
+    }
+    final StackTraceElement details = throwable.getStackTrace()[0];
+    writer.append("<h2>").append(throwable.getClass().getCanonicalName()).append("</h2>\n")
+        .append("<p>")
+        .append(ODataDebugResponseWrapper.escapeHtml(getMessageText(throwable)))
+        .append("</p>\n");
+    appendStackTraceElement(details, true, true, writer);
+  }
+
+  private void appendStackTraceElement(final StackTraceElement stackTraceElement,
+      final boolean isFirst, final boolean isLast, Writer writer) throws IOException {
+    if (isFirst) {
+      writer.append("<table>\n<thead>\n")
+          .append("<tr>\n<th class=\"name\">Class</th>\n")
+          .append("<th class=\"name\">Method</th>\n")
+          .append("<th class=\"value\">Line number in class</th>\n</tr>\n")
+          .append("</thead>\n<tbody>\n");
+    }
+    writer.append("<tr>\n<td class=\"name\">").append(stackTraceElement.getClassName()).append("</td>\n")
+        .append("<td class=\"name\">").append(stackTraceElement.getMethodName()).append("</td>\n")
+        .append("<td class=\"value\">").append(Integer.toString(stackTraceElement.getLineNumber()))
+        .append("</td>\n</tr>\n");
+    if (isLast) {
+      writer.append("</tbody>\n</table>\n");
+    }
+  }
+
+  private String getMessageText(final Throwable throwable) {
+    return throwable instanceof ODataMessageException ?
+        MessageService.getMessage(locale, ((ODataMessageException) throwable).getMessageReference()).getText() :
+        throwable.getLocalizedMessage() == null ? "Exception text missing" : throwable.getLocalizedMessage();
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRequest.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRequest.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRequest.java
index 94680e0..62af105 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRequest.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRequest.java
@@ -19,6 +19,7 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
@@ -26,17 +27,20 @@ import java.util.Map;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 
 /**
- *  
+ * Request debug information.
  */
 public class DebugInfoRequest implements DebugInfo {
 
   private final String method;
   private final URI uri;
+  private final String protocol;
   private final Map<String, List<String>> headers;
 
-  public DebugInfoRequest(final String method, final URI uri, final Map<String, List<String>> headers) {
+  public DebugInfoRequest(final String method, final URI uri, final String protocol,
+      final Map<String, List<String>> headers) {
     this.method = method;
     this.uri = uri;
+    this.protocol = protocol;
     this.headers = headers;
   }
 
@@ -48,8 +52,9 @@ public class DebugInfoRequest implements DebugInfo {
   @Override
   public void appendJson(final JsonStreamWriter jsonStreamWriter) throws IOException {
     jsonStreamWriter.beginObject()
-        .namedStringValueRaw("method", method).separator()
-        .namedStringValue("uri", uri.toString());
+        .namedStringValueRaw("method", method.toString()).separator()
+        .namedStringValue("uri", uri.toString()).separator()
+        .namedStringValue("protocol", protocol);
 
     if (!headers.isEmpty()) {
       jsonStreamWriter.separator()
@@ -58,6 +63,9 @@ public class DebugInfoRequest implements DebugInfo {
       boolean first = true;
       for (final String name : headers.keySet()) {
         for (final String value : headers.get(name)) {
+          if (value == null) {
+            continue;
+          }
           if (!first) {
             jsonStreamWriter.separator();
           }
@@ -70,4 +78,28 @@ public class DebugInfoRequest implements DebugInfo {
 
     jsonStreamWriter.endObject();
   }
+
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    writer.append("<h2>Request Method</h2>\n")
+        .append("<p>").append(method).append("</p>\n")
+        .append("<h2>Request URI</h2>\n")
+        .append("<p>").append(ODataDebugResponseWrapper.escapeHtml(uri.toString())).append("</p>\n")
+        .append("<h2>Request Protocol</h2>\n")
+        .append("<p>").append(protocol).append("</p>\n");
+    writer.append("<h2>Request Headers</h2>\n")
+        .append("<table>\n<thead>\n")
+        .append("<tr><th class=\"name\">Name</th><th class=\"value\">Value</th></tr>\n")
+        .append("</thead>\n<tbody>\n");
+    for (final String name : headers.keySet()) {
+      for (final String value : headers.get(name)) {
+        if (value != null) {
+          writer.append("<tr><td class=\"name\">").append(name).append("</td>")
+              .append("<td class=\"value\">").append(ODataDebugResponseWrapper.escapeHtml(value))
+              .append("</td></tr>\n");
+        }
+      }
+    }
+    writer.append("</tbody>\n</table>\n");
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoResponse.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoResponse.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoResponse.java
index 154ba1b..a2e1d42 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoResponse.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoResponse.java
@@ -19,22 +19,32 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
+import org.apache.olingo.odata2.api.processor.ODataResponse;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 
 /**
- *  
+ * Response debug information.
  */
 public class DebugInfoResponse implements DebugInfo {
 
+  private final ODataResponse response;
+  private final String serviceRoot;
   private final HttpStatusCodes status;
   private final Map<String, String> headers;
 
-  public DebugInfoResponse(final HttpStatusCodes status, final Map<String, String> headers) {
-    this.status = status;
-    this.headers = headers;
+  public DebugInfoResponse(final ODataResponse response, final String serviceRoot) {
+    this.response = response;
+    this.serviceRoot = serviceRoot;
+    status = response.getStatus();
+    headers = new HashMap<String, String>();
+    for (final String name : response.getHeaderNames()) {
+      headers.put(name, response.getHeader(name));
+    }
   }
 
   @Override
@@ -43,7 +53,7 @@ public class DebugInfoResponse implements DebugInfo {
   }
 
   @Override
-  public void appendJson(final JsonStreamWriter jsonStreamWriter) throws IOException {
+  public void appendJson(JsonStreamWriter jsonStreamWriter) throws IOException {
     jsonStreamWriter.beginObject();
 
     if (status != null) {
@@ -58,20 +68,31 @@ public class DebugInfoResponse implements DebugInfo {
       if (status != null) {
         jsonStreamWriter.separator();
       }
+      jsonStreamWriter.name("headers");
+      ODataDebugResponseWrapper.appendJsonTable(jsonStreamWriter, headers);
+    }
 
-      jsonStreamWriter.name("headers")
-          .beginObject();
-      boolean first = true;
-      for (final String name : headers.keySet()) {
-        if (!first) {
-          jsonStreamWriter.separator();
-        }
-        first = false;
-        jsonStreamWriter.namedStringValue(name, headers.get(name));
+    if (response.getContentHeader() != null && response.getEntity() != null) {
+      if (status != null || !headers.isEmpty()) {
+        jsonStreamWriter.separator();
       }
-      jsonStreamWriter.endObject();
+      jsonStreamWriter.name("body");
+      new DebugInfoBody(response, serviceRoot).appendJson(jsonStreamWriter);
     }
 
     jsonStreamWriter.endObject();
   }
+
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    writer.append("<h2>Status Code</h2>\n")
+        .append("<p>").append(Integer.toString(status.getStatusCode())).append(' ')
+        .append(status.getInfo()).append("</p>\n")
+        .append("<h2>Response Headers</h2>\n");
+    ODataDebugResponseWrapper.appendHtmlTable(writer, headers);
+    if (response.getContentHeader() != null && response.getEntity() != null) {
+      writer.append("<h2>Response Body</h2>\n");
+      new DebugInfoBody(response, serviceRoot).appendHtml(writer);
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRuntime.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRuntime.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRuntime.java
index fadde0c..250a2b4 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRuntime.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoRuntime.java
@@ -19,6 +19,7 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
+import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -27,7 +28,7 @@ import org.apache.olingo.odata2.api.processor.ODataContext.RuntimeMeasurement;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 
 /**
- *  
+ * Runtime debug information.
  */
 public class DebugInfoRuntime implements DebugInfo {
 
@@ -126,16 +127,20 @@ public class DebugInfoRuntime implements DebugInfo {
     jsonStreamWriter.beginObject()
         .namedStringValueRaw("class", node.className).separator()
         .namedStringValueRaw("method", node.methodName).separator()
-        .name("duration")
-        .unquotedValue(node.timeStopped == 0 ? null :
-            Long.toString((node.timeStopped - node.timeStarted) / 1000))
-        .separator()
-        .name("memory")
-        .unquotedValue(node.memoryStopped == 0 ? null :
-            Long.toString((node.memoryStopped - node.memoryStarted) / 1000))
-        .separator()
-        .name("children");
-    appendJsonChildren(jsonStreamWriter, node);
+        .name("duration");
+    if (node.timeStopped == 0) {
+      jsonStreamWriter.unquotedValue(null);
+    } else {
+      jsonStreamWriter.beginObject()
+          .name("value").unquotedValue(Long.toString((node.timeStopped - node.timeStarted) / 1000)).separator()
+          .namedStringValueRaw("unit", "µs")
+          .endObject();
+    }
+    if (!node.children.isEmpty()) {
+      jsonStreamWriter.separator()
+          .name("children");
+      appendJsonChildren(jsonStreamWriter, node);
+    }
     jsonStreamWriter.endObject();
   }
 
@@ -152,4 +157,40 @@ public class DebugInfoRuntime implements DebugInfo {
     }
     jsonStreamWriter.endArray();
   }
+
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    appendRuntimeNode(rootNode, "", true, writer);
+  }
+
+  private void appendRuntimeNode(final RuntimeNode node, final String draw, final boolean isLast, Writer writer)
+      throws IOException {
+    if (node.className != null) {
+      writer.append("<li>")
+          .append("<span class=\"code\">")
+          .append("<span class=\"draw\">").append(draw)
+          .append(isLast ? "&#x2514;" : "&#x251C;").append("&#x2500;&nbsp;</span>")
+          .append("<span class=\"class\">").append(node.className).append("</span>.")
+          .append("<span class=\"method\">").append(node.methodName).append("(&hellip;)")
+          .append("</span></span>");
+      long time = node.timeStopped == 0 ? 0 : (node.timeStopped - node.timeStarted) / 1000;
+      writer.append("<span class=\"").append(time == 0 ? "null" : "numeric")
+          .append("\" title=\"").append(time == 0 ? "Stop time missing" : "Gross duration")
+          .append("\">").append(time == 0 ? "unfinished" : Long.toString(time) + "&nbsp;&micro;s")
+          .append("</span>\n");
+    }
+    if (!node.children.isEmpty()) {
+      writer.append("<ol class=\"tree\">\n");
+      for (final RuntimeNode childNode : node.children) {
+        appendRuntimeNode(childNode,
+            node.className == null ? draw : draw + (isLast ? "&nbsp;" : "&#x2502;") + "&nbsp;&nbsp;",
+            node.children.indexOf(childNode) == node.children.size() - 1,
+            writer);
+      }
+      writer.append("</ol>\n");
+    }
+    if (node.className != null) {
+      writer.append("</li>\n");
+    }
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoServer.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoServer.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoServer.java
new file mode 100644
index 0000000..97d5646
--- /dev/null
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoServer.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.odata2.core.debug;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
+
+/**
+ * Server debug information.
+ */
+public class DebugInfoServer implements DebugInfo {
+
+  private final Map<String, String> environment;
+
+  public DebugInfoServer(final HttpServletRequest httpServletRequest) {
+    environment = new TreeMap<String, String>();
+    environment.put("authType", httpServletRequest.getAuthType());
+    environment.put("localAddr", httpServletRequest.getLocalAddr());
+    environment.put("localName", httpServletRequest.getLocalName());
+    addInt("localPort", httpServletRequest.getLocalPort());
+    environment.put("pathInfo", httpServletRequest.getPathInfo());
+    environment.put("pathTranslated", httpServletRequest.getPathTranslated());
+    environment.put("remoteAddr", httpServletRequest.getRemoteAddr());
+    environment.put("remoteHost", httpServletRequest.getRemoteHost());
+    addInt("remotePort", httpServletRequest.getRemotePort());
+    environment.put("remoteUser", httpServletRequest.getRemoteUser());
+    environment.put("scheme", httpServletRequest.getScheme());
+    environment.put("serverName", httpServletRequest.getServerName());
+    addInt("serverPort", httpServletRequest.getServerPort());
+    environment.put("servletPath", httpServletRequest.getServletPath());
+  }
+
+  @Override
+  public String getName() {
+    return "Environment";
+  }
+
+  @Override
+  public void appendJson(final JsonStreamWriter jsonStreamWriter) throws IOException {
+    ODataDebugResponseWrapper.appendJsonTable(jsonStreamWriter, environment);
+  }
+
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    writer.append("<h2>Server Environment</h2>\n");
+    ODataDebugResponseWrapper.appendHtmlTable(writer, environment);
+  }
+
+  private void addInt(final String name, final int number) {
+    environment.put(name, number == 0 ? null : Integer.toString(number));
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoUri.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoUri.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoUri.java
index 21ca068..daecc7a 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoUri.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/DebugInfoUri.java
@@ -19,35 +19,55 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.IOException;
+import java.io.Writer;
 
 import org.apache.olingo.odata2.api.edm.EdmException;
+import org.apache.olingo.odata2.api.edm.EdmProperty;
 import org.apache.olingo.odata2.api.exception.ODataApplicationException;
+import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode;
 import org.apache.olingo.odata2.api.uri.UriInfo;
+import org.apache.olingo.odata2.api.uri.expression.BinaryExpression;
+import org.apache.olingo.odata2.api.uri.expression.CommonExpression;
 import org.apache.olingo.odata2.api.uri.expression.ExceptionVisitExpression;
+import org.apache.olingo.odata2.api.uri.expression.ExpressionKind;
 import org.apache.olingo.odata2.api.uri.expression.ExpressionParserException;
 import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
+import org.apache.olingo.odata2.api.uri.expression.MemberExpression;
+import org.apache.olingo.odata2.api.uri.expression.MethodExpression;
 import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
+import org.apache.olingo.odata2.api.uri.expression.OrderExpression;
+import org.apache.olingo.odata2.api.uri.expression.UnaryExpression;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 import org.apache.olingo.odata2.core.uri.ExpandSelectTreeCreator;
 import org.apache.olingo.odata2.core.uri.ExpandSelectTreeNodeImpl;
 import org.apache.olingo.odata2.core.uri.expression.JsonVisitor;
 
 /**
- *  
+ * URI parser debug information.
  */
 public class DebugInfoUri implements DebugInfo {
 
   private final UriInfo uriInfo;
+  private final FilterExpression filter;
+  private final OrderByExpression orderBy;
+  private final ExpandSelectTreeNodeImpl expandSelectTree;
   private final ExpressionParserException exception;
 
-  public DebugInfoUri(final UriInfo uriInfo, final Exception exception) {
+  public DebugInfoUri(final UriInfo uriInfo, final ExpressionParserException exception) {
     this.uriInfo = uriInfo;
+    filter = uriInfo == null ? null : uriInfo.getFilter();
+    orderBy = uriInfo == null ? null : uriInfo.getOrderBy();
+    expandSelectTree = uriInfo == null ? null : getExpandSelect();
+    this.exception = exception;
+  }
 
-    Throwable candidate = exception;
-    while (candidate != null && !(candidate instanceof ExpressionParserException)) {
-      candidate = candidate.getCause();
+  private ExpandSelectTreeNodeImpl getExpandSelect() {
+    try {
+      return uriInfo.getExpand().isEmpty() && uriInfo.getSelect().isEmpty() ? null :
+          new ExpandSelectTreeCreator(uriInfo.getSelect(), uriInfo.getExpand()).create();
+    } catch (final EdmException e) {
+      return null;
     }
-    this.exception = (ExpressionParserException) candidate;
   }
 
   @Override
@@ -56,72 +76,156 @@ public class DebugInfoUri implements DebugInfo {
   }
 
   @Override
-  public void appendJson(final JsonStreamWriter jsonStreamWriter) throws IOException {
+  public void appendJson(JsonStreamWriter jsonStreamWriter) throws IOException {
     jsonStreamWriter.beginObject();
 
-    if (exception != null) {
+    if (exception != null && exception.getFilterTree() != null) {
       jsonStreamWriter.name("error")
-          .beginObject();
-      if (exception.getFilterTree() != null) {
-        jsonStreamWriter.namedStringValue("filter", exception.getFilterTree().getUriLiteral());
+          .beginObject()
+          .namedStringValue("expression", exception.getFilterTree().getUriLiteral())
+          .endObject();
+      if (filter != null || orderBy != null || expandSelectTree != null) {
+        jsonStreamWriter.separator();
       }
-      jsonStreamWriter.endObject();
     }
 
-    if (uriInfo != null) {
-      if (exception != null
-          && (uriInfo.getFilter() != null || uriInfo.getOrderBy() != null
-              || !uriInfo.getExpand().isEmpty() || !uriInfo.getSelect().isEmpty())) {
+    if (filter != null) {
+      String filterString;
+      try {
+        filterString = (String) filter.accept(new JsonVisitor());
+      } catch (final ExceptionVisitExpression e) {
+        filterString = null;
+      } catch (final ODataApplicationException e) {
+        filterString = null;
+      }
+      jsonStreamWriter.name("filter").unquotedValue(filterString);
+      if (orderBy != null || expandSelectTree != null) {
         jsonStreamWriter.separator();
       }
+    }
 
-      final FilterExpression filter = uriInfo.getFilter();
-      if (filter != null) {
-        String filterString;
-        try {
-          filterString = (String) filter.accept(new JsonVisitor());
-        } catch (final ExceptionVisitExpression e) {
-          filterString = null;
-        } catch (final ODataApplicationException e) {
-          filterString = null;
-        }
-        jsonStreamWriter.name("filter").unquotedValue(filterString);
-        if (uriInfo.getOrderBy() != null
-            || !uriInfo.getExpand().isEmpty() || !uriInfo.getSelect().isEmpty()) {
-          jsonStreamWriter.separator();
-        }
+    if (orderBy != null) {
+      String orderByString;
+      try {
+        orderByString = (String) orderBy.accept(new JsonVisitor());
+      } catch (final ExceptionVisitExpression e) {
+        orderByString = null;
+      } catch (final ODataApplicationException e) {
+        orderByString = null;
       }
+      jsonStreamWriter.name("orderby").unquotedValue(orderByString);
+      if (expandSelectTree != null) {
+        jsonStreamWriter.separator();
+      }
+    }
 
-      final OrderByExpression orderBy = uriInfo.getOrderBy();
-      if (orderBy != null) {
-        String orderByString;
-        try {
-          orderByString = (String) orderBy.accept(new JsonVisitor());
-        } catch (final ExceptionVisitExpression e) {
-          orderByString = null;
-        } catch (final ODataApplicationException e) {
-          orderByString = null;
+    if (expandSelectTree != null) {
+      jsonStreamWriter.name("expandSelect").unquotedValue(expandSelectTree.toJsonString());
+    }
+
+    jsonStreamWriter.endObject();
+  }
+
+  @Override
+  public void appendHtml(Writer writer) throws IOException {
+    if (exception != null && exception.getFilterTree() != null) {
+      writer.append("<h2>Expression Information</h2>\n")
+          .append("<pre class=\"code\">").append(exception.getFilterTree().getUriLiteral())
+          .append("</pre>\n");
+      // TODO: filter error position, filter tokens, filter tree
+    }
+    if (filter != null) {
+      writer.append("<h2>Filter</h2>\n")
+          .append("<ul class=\"expr\"><li>");
+      appendExpression(filter.getExpression(), writer);
+      writer.append("</li></ul>\n");
+    }
+    if (orderBy != null) {
+      writer.append("<h2>Orderby</h2>\n")
+          .append(orderBy.getOrdersCount() == 1 ? "<ul" : "<ol").append(" class=\"expr\">\n");
+      for (final OrderExpression order : orderBy.getOrders()) {
+        writer.append("<li>");
+        appendExpression(order.getExpression(), writer);
+        final ExpressionKind kind = order.getExpression().getKind();
+        if (kind == ExpressionKind.PROPERTY || kind == ExpressionKind.LITERAL) {
+          writer.append("<br />");
         }
-        jsonStreamWriter.name("orderby").unquotedValue(orderByString);
-        if (!uriInfo.getExpand().isEmpty() || !uriInfo.getSelect().isEmpty()) {
-          jsonStreamWriter.separator();
+        writer.append("<span class=\"order\">")
+            .append(order.getSortOrder().toString())
+            .append("</span></li>\n");
+      }
+      writer.append(orderBy.getOrdersCount() == 1 ? "</ul" : "</ol").append(">\n");
+    }
+    if (expandSelectTree != null) {
+      writer.append("<h2>Expand/Select</h2>\n");
+      appendExpandSelect(expandSelectTree, writer);
+    }
+  }
+
+  private void appendExpression(final CommonExpression expression, Writer writer) throws IOException {
+    final ExpressionKind kind = expression.getKind();
+    writer.append("<span class=\"kind\">")
+        .append(kind.toString())
+        .append("</span> <span class=\"literal\">")
+        .append(kind == ExpressionKind.MEMBER ? ((MemberExpression) expression).getProperty().getUriLiteral() :
+            expression.getUriLiteral())
+        .append("</span>, type <span class=\"type\">")
+        .append(expression.getEdmType().toString())
+        .append("</span>");
+    if (kind == ExpressionKind.UNARY) {
+      writer.append("<ul class=\"expr\"><li>");
+      appendExpression(((UnaryExpression) expression).getOperand(), writer);
+      writer.append("</li></ul>");
+    } else if (kind == ExpressionKind.BINARY) {
+      writer.append("<ol class=\"expr\"><li>");
+      appendExpression(((BinaryExpression) expression).getLeftOperand(), writer);
+      writer.append("</li><li>");
+      appendExpression(((BinaryExpression) expression).getRightOperand(), writer);
+      writer.append("</li></ol>");
+    } else if (kind == ExpressionKind.METHOD) {
+      final MethodExpression methodExpression = (MethodExpression) expression;
+      if (methodExpression.getParameterCount() > 0) {
+        writer.append("<ol class=\"expr\">");
+        for (final CommonExpression parameter : methodExpression.getParameters()) {
+          writer.append("<li>");
+          appendExpression(parameter, writer);
+          writer.append("</li>");
         }
+        writer.append("</ol>");
       }
+    } else if (kind == ExpressionKind.MEMBER) {
+      writer.append("<ul class=\"expr\"><li>");
+      appendExpression(((MemberExpression) expression).getPath(), writer);
+      writer.append("</li></ul>");
+    }
+  }
 
-      if (!uriInfo.getExpand().isEmpty() || !uriInfo.getSelect().isEmpty()) {
-        String expandSelectString;
+  private void appendExpandSelect(final ExpandSelectTreeNode expandSelect, Writer writer) throws IOException {
+    writer.append("<ul class=\"expand\">\n")
+        .append("<li>");
+    if (expandSelect.isAll()) {
+      writer.append("all properties");
+    } else {
+      for (final EdmProperty property : expandSelect.getProperties()) {
         try {
-          ExpandSelectTreeCreator expandSelectCreator =
-              new ExpandSelectTreeCreator(uriInfo.getSelect(), uriInfo.getExpand());
-          final ExpandSelectTreeNodeImpl expandSelectTree = expandSelectCreator.create();
-          expandSelectString = expandSelectTree.toJsonString();
-        } catch (final EdmException e) {
-          expandSelectString = null;
+          writer.append("property <span class=\"prop\">")
+              .append(property.getName())
+              .append("</span><br />");
+        } catch (final EdmException e) {}
+      }
+    }
+    writer.append("</li>\n");
+    if (!expandSelect.getLinks().isEmpty()) {
+      for (final String name : expandSelect.getLinks().keySet()) {
+        writer.append("<li>link <span class=\"link\">").append(name).append("</span>");
+        final ExpandSelectTreeNode link = expandSelect.getLinks().get(name);
+        if (link != null) {
+          writer.append('\n');
+          appendExpandSelect(link, writer);
         }
-        jsonStreamWriter.name("expand/select").unquotedValue(expandSelectString);
+        writer.append("</li>\n");
       }
     }
-
-    jsonStreamWriter.endObject();
+    writer.append("</ul>\n");
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapper.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapper.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapper.java
index f4d9482..5eae0e9 100644
--- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapper.java
+++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapper.java
@@ -19,21 +19,28 @@
 package org.apache.olingo.odata2.core.debug;
 
 import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.olingo.odata2.api.commons.HttpContentType;
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
 import org.apache.olingo.odata2.api.exception.ODataException;
 import org.apache.olingo.odata2.api.processor.ODataContext;
 import org.apache.olingo.odata2.api.processor.ODataResponse;
+import org.apache.olingo.odata2.api.uri.PathInfo;
 import org.apache.olingo.odata2.api.uri.UriInfo;
+import org.apache.olingo.odata2.api.uri.expression.ExpressionParserException;
 import org.apache.olingo.odata2.core.ep.util.CircleStreamBuffer;
 import org.apache.olingo.odata2.core.ep.util.JsonStreamWriter;
 import org.apache.olingo.odata2.core.exception.MessageService;
@@ -47,25 +54,35 @@ public class ODataDebugResponseWrapper {
 
   public static final String ODATA_DEBUG_QUERY_PARAMETER = "odata-debug";
   public static final String ODATA_DEBUG_JSON = "json";
+  public static final String ODATA_DEBUG_HTML = "html";
+  public static final String ODATA_DEBUG_DOWNLOAD = "download";
 
   private final ODataContext context;
   private final ODataResponse response;
   private final UriInfo uriInfo;
   private final Exception exception;
+  private final boolean isJson;
+  private final boolean isDownload;
 
   public ODataDebugResponseWrapper(final ODataContext context, final ODataResponse response, final UriInfo uriInfo,
-      final Exception exception) {
+      final Exception exception, final String debugValue) {
     this.context = context;
     this.response = response;
     this.uriInfo = uriInfo;
     this.exception = exception;
+    isJson = ODATA_DEBUG_JSON.equals(debugValue);
+    isDownload = ODATA_DEBUG_DOWNLOAD.equals(debugValue);
   }
 
   public ODataResponse wrapResponse() {
     try {
+      final List<DebugInfo> parts = createParts();
       return ODataResponse.status(HttpStatusCodes.OK)
-          .entity(wrapInJson(createParts()))
-          .contentHeader(HttpContentType.APPLICATION_JSON)
+          .entity(isJson ? wrapInJson(parts) : wrapInHtml(parts))
+          .contentHeader(isJson ? HttpContentType.APPLICATION_JSON : HttpContentType.TEXT_HTML)
+          .header("Content-Disposition", isDownload ?
+              "attachment; filename=OData-Response." + new Date().toString().replace(' ', '_').replace(':', '.')
+                  + ".html" : null)
           .build();
     } catch (final ODataException e) {
       throw new ODataRuntimeException("Should not happen", e);
@@ -77,28 +94,32 @@ public class ODataDebugResponseWrapper {
   private List<DebugInfo> createParts() throws ODataException {
     List<DebugInfo> parts = new ArrayList<DebugInfo>();
 
-    // body
-    if (response.getContentHeader() != null && response.getEntity() != null) {
-      parts.add(new DebugInfoBody(response));
-    }
-
     // request
-    parts.add(new DebugInfoRequest(context.getHttpMethod(),
-        context.getPathInfo().getRequestUri(),
+    final HttpServletRequest servletRequest =
+        (HttpServletRequest) context.getParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT);
+    final String protocol = servletRequest == null ? null : servletRequest.getProtocol();
+    parts.add(new DebugInfoRequest(context.getHttpMethod(), context.getPathInfo().getRequestUri(), protocol,
         context.getRequestHeaders()));
 
     // response
-    Map<String, String> responseHeaders = new HashMap<String, String>();
-    for (final String name : response.getHeaderNames()) {
-      responseHeaders.put(name, response.getHeader(name));
+    parts.add(new DebugInfoResponse(response, context.getPathInfo().getServiceRoot().toASCIIString()));
+
+    // server
+    if (servletRequest != null) {
+      parts.add(new DebugInfoServer(servletRequest));
     }
-    parts.add(new DebugInfoResponse(response.getStatus(), responseHeaders));
 
     // URI
+    Throwable candidate = exception;
+    while (candidate != null && !(candidate instanceof ExpressionParserException)) {
+      candidate = candidate.getCause();
+    }
+    final ExpressionParserException expressionParserException = (ExpressionParserException) candidate;
     if (uriInfo != null
         && (uriInfo.getFilter() != null || uriInfo.getOrderBy() != null
-            || !uriInfo.getExpand().isEmpty() || !uriInfo.getSelect().isEmpty())) {
-      parts.add(new DebugInfoUri(uriInfo, exception));
+            || !uriInfo.getExpand().isEmpty() || !uriInfo.getSelect().isEmpty())
+        || expressionParserException != null && expressionParserException.getFilterTree() != null) {
+      parts.add(new DebugInfoUri(uriInfo, expressionParserException));
     }
 
     // runtime measurements
@@ -120,8 +141,18 @@ public class ODataDebugResponseWrapper {
     BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(csb.getOutputStream(), "UTF-8"));
     JsonStreamWriter jsonStreamWriter = new JsonStreamWriter(writer);
     jsonStreamWriter.beginObject();
+    jsonStreamWriter.name(parts.get(0).getName().toLowerCase(Locale.ROOT));
+    parts.get(0).appendJson(jsonStreamWriter);
+    jsonStreamWriter.separator();
+    jsonStreamWriter.name(parts.get(1).getName().toLowerCase(Locale.ROOT));
+    parts.get(1).appendJson(jsonStreamWriter);
+    if (parts.size() > 2) {
+      jsonStreamWriter.separator();
+      jsonStreamWriter.name("server")
+          .beginObject();
+    }
     boolean first = true;
-    for (final DebugInfo part : parts) {
+    for (final DebugInfo part : parts.subList(2, parts.size())) {
       if (!first) {
         jsonStreamWriter.separator();
       }
@@ -129,9 +160,119 @@ public class ODataDebugResponseWrapper {
       jsonStreamWriter.name(part.getName().toLowerCase(Locale.ROOT));
       part.appendJson(jsonStreamWriter);
     }
+    if (parts.size() > 2) {
+      jsonStreamWriter.endObject();
+    }
     jsonStreamWriter.endObject();
     writer.flush();
     csb.closeWrite();
     return csb.getInputStream();
   }
+
+  private InputStream wrapInHtml(final List<DebugInfo> parts) throws IOException {
+    StringWriter writer = new StringWriter();
+    PathInfo pathInfo = null;
+    try {
+      pathInfo = context.getPathInfo();
+    } catch (final ODataException e) {}
+
+    writer.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"\n")
+        .append("  \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n")
+        .append("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n")
+        .append("<head>\n")
+        .append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n")
+        .append("<title>")
+        .append(pathInfo == null ? "" :
+            escapeHtml(pathInfo.getServiceRoot().relativize(pathInfo.getRequestUri()).getPath()))
+        .append("</title>\n")
+        .append("<style type=\"text/css\">\n")
+        .append("body { font-family: Arial, sans-serif; font-size: 13px;\n")
+        .append("       line-height: 16px; margin: 0;\n")
+        .append("       background-color: #eeeeee; color: #333333; }\n")
+        .append(".header { float: left; }\n")
+        .append(".header a { line-height: 22px; padding: 10px 18px;\n")
+        .append("            text-decoration: none; color: #333333; }\n")
+        .append(":target, .header:nth-last-child(2) { background-color: #cccccc; }\n")
+        .append(":target ~ .header:nth-last-child(2) { background-color: inherit; }\n")
+        .append(".header:focus, .header:hover,\n")
+        .append("  .header:nth-last-child(2):focus, .header:nth-last-child(2):hover\n")
+        .append("    { background-color: #999999; }\n")
+        .append(".section { position: absolute; top: 42px; min-width: 100%;\n")
+        .append("           padding-top: 18px; border-top: 1px solid #dddddd; }\n")
+        .append(".section > * { margin-left: 18px; }\n")
+        .append(":target + .section, .section:last-child { display: block; }\n")
+        .append(".section, :target + .section ~ .section { display: none; }\n")
+        .append("h1 { font-size: 18px; font-weight: normal; margin: 10px 0; }\n")
+        .append("h2 { font-size: 15px; }\n")
+        .append("h2:not(:first-child) { margin-top: 2em; }\n")
+        .append("table { border-collapse: collapse; border-spacing: 0;\n")
+        .append("        margin-top: 1.5em; }\n")
+        .append("table, thead { border-width: 1px 0; border-style: solid;\n")
+        .append("               border-color: #dddddd; text-align: left; }\n")
+        .append("th.name, td.name { padding: 1ex 2em 1ex 0; }\n")
+        .append("tbody > tr:hover { background-color: #cccccc; }\n")
+        .append(".code { font-family: \"Courier New\", monospace; }\n")
+        .append(".code, .tree li { line-height: 15px; }\n")
+        .append(".code a { text-decoration: underline; color: #666666; }\n")
+        .append(".xml .ns { font-style: italic; color: #999999; }\n")
+        .append("ul, .tree { list-style-type: none; }\n")
+        .append("div > ul.expr, div > .expand, .tree { padding-left: 0; }\n")
+        .append(".expr, .expand, .null, .numeric { padding-left: 1.5em; }\n")
+        .append("</style>\n")
+        .append("</head>\n")
+        .append("<body>\n");
+    char count = '0';
+    for (final DebugInfo part : parts) {
+      writer.append("<div class=\"header\" id=\"sec").append(++count).append("\">\n")
+          .append("<h1><a href=\"#sec").append(count).append("\">")
+          .append(part.getName())
+          .append("</a></h1>\n")
+          .append("</div>\n")
+          .append("<div class=\"section\">\n");
+      part.appendHtml(writer);
+      writer.append("</div>\n");
+    }
+    writer.append("</body>\n")
+        .append("</html>\n")
+        .close();
+    return new ByteArrayInputStream(writer.toString().getBytes("UTF-8"));
+  }
+
+  protected static String escapeHtml(final String value) {
+    return value == null ? null : value.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
+  }
+
+  protected static void appendJsonTable(JsonStreamWriter jsonStreamWriter, final Map<String, String> entries)
+      throws IOException {
+    jsonStreamWriter.beginObject();
+    boolean first = true;
+    for (final String name : entries.keySet()) {
+      final String value = entries.get(name);
+      if (value == null) {
+        continue;
+      }
+      if (!first) {
+        jsonStreamWriter.separator();
+      }
+      first = false;
+      jsonStreamWriter.namedStringValue(name, value);
+    }
+    jsonStreamWriter.endObject();
+  }
+
+  protected static void appendHtmlTable(Writer writer, final Map<String, String> entries) throws IOException {
+    writer.append("<table>\n<thead>\n")
+        .append("<tr><th class=\"name\">Name</th><th class=\"value\">Value</th></tr>\n")
+        .append("</thead>\n<tbody>\n");
+    for (final String name : entries.keySet()) {
+      final String value = entries.get(name);
+      if (value != null) {
+        writer.append("<tr><td class=\"name\">").append(name).append("</td>")
+            .append("<td class=\"value\">")
+            .append(ODataDebugResponseWrapper.escapeHtml(value))
+            .append("</td></tr>\n");
+      }
+    }
+    writer.append("</tbody>\n</table>\n");
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/DebugInfoBodyTest.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/DebugInfoBodyTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/DebugInfoBodyTest.java
index 954a310..0d99059 100644
--- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/DebugInfoBodyTest.java
+++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/DebugInfoBodyTest.java
@@ -57,13 +57,13 @@ public class DebugInfoBodyTest {
     ODataResponse response = mock(ODataResponse.class);
     ByteArrayInputStream in = new ByteArrayInputStream(STRING_CONTENT.getBytes());
     when(response.getEntity()).thenReturn(in);
-    when(response.getContentHeader()).thenReturn(HttpContentType.APPLICATION_OCTET_STREAM);
+    when(response.getContentHeader()).thenReturn(HttpContentType.TEXT_PLAIN);
     assertEquals(STRING_CONTENT_JSON, appendJson(response));
 
-    in = new ByteArrayInputStream(STRING_CONTENT.getBytes());
+    in = new ByteArrayInputStream(STRING_CONTENT.getBytes("UTF-8"));
     when(response.getEntity()).thenReturn(in);
     when(response.getContentHeader()).thenReturn("image/png");
-    assertEquals("\"" + Base64.encodeBase64String(STRING_CONTENT.getBytes()) + "\"",
+    assertEquals("\"" + Base64.encodeBase64String(STRING_CONTENT.getBytes("UTF-8")) + "\"",
         appendJson(response));
   }
 
@@ -78,7 +78,7 @@ public class DebugInfoBodyTest {
 
   private String appendJson(final ODataResponse response) throws IOException {
     Writer writer = new StringWriter();
-    DebugInfoBody body = new DebugInfoBody(response);
+    DebugInfoBody body = new DebugInfoBody(response, null);
     body.appendJson(new JsonStreamWriter(writer));
     return writer.toString();
   }

http://git-wip-us.apache.org/repos/asf/incubator-olingo-odata2/blob/6b9a4e67/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapperTest.java
----------------------------------------------------------------------
diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapperTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapperTest.java
index e033887..1442b5f 100644
--- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapperTest.java
+++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/debug/ODataDebugResponseWrapperTest.java
@@ -19,6 +19,7 @@
 package org.apache.olingo.odata2.core.debug;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -32,6 +33,8 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.olingo.odata2.api.commons.HttpContentType;
 import org.apache.olingo.odata2.api.commons.HttpHeaders;
 import org.apache.olingo.odata2.api.commons.HttpStatusCodes;
@@ -58,12 +61,11 @@ import org.junit.Test;
 
 /**
  * Tests for the debug information output.
- * 
  */
 public class ODataDebugResponseWrapperTest extends BaseTest {
 
   private static final String EXPECTED = "{"
-      + "\"request\":{\"method\":\"GET\",\"uri\":\"http://test/entity\"},"
+      + "\"request\":{\"method\":\"GET\",\"uri\":\"http://test/entity\",\"protocol\":null},"
       + "\"response\":{\"status\":{\"code\":200,\"info\":\"OK\"}}}";
 
   private ODataContext mockContext(final ODataHttpMethod method) throws ODataException {
@@ -71,6 +73,7 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     when(context.getHttpMethod()).thenReturn(method.name());
     PathInfo pathInfo = mock(PathInfo.class);
     when(pathInfo.getRequestUri()).thenReturn(URI.create("http://test/entity"));
+    when(pathInfo.getServiceRoot()).thenReturn(URI.create("http://test/"));
     when(context.getPathInfo()).thenReturn(pathInfo);
     when(context.getRuntimeMeasurements()).thenReturn(null);
     return context;
@@ -88,36 +91,33 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     return response;
   }
 
-  private RuntimeMeasurement mockRuntimeMeasurement(final String method, final long startTime, final long stopTime,
-      final long startMemory, final long stopMemory) {
+  private RuntimeMeasurement mockRuntimeMeasurement(final String method, final long startTime, final long stopTime) {
     RuntimeMeasurement measurement = mock(RuntimeMeasurement.class);
     when(measurement.getClassName()).thenReturn("class");
     when(measurement.getMethodName()).thenReturn(method);
     when(measurement.getTimeStarted()).thenReturn(startTime);
     when(measurement.getTimeStopped()).thenReturn(stopTime);
-    when(measurement.getMemoryStarted()).thenReturn(startMemory);
-    when(measurement.getMemoryStopped()).thenReturn(stopMemory);
     return measurement;
   }
 
-  private RuntimeMeasurement mockRuntimeMeasurement(final String method, final long start, final long stop) {
-    return mockRuntimeMeasurement(method, start, stop, 1000, 4000);
-  }
-
   @Test
   public void minimal() throws Exception {
     final ODataContext context = mockContext(ODataHttpMethod.PUT);
     final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.NO_CONTENT, null, null);
 
-    final ODataResponse response =
-        new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null)
-            .wrapResponse();
-    String actualJson = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
+    final String actualJson = StringHelper.inputStreamToString((InputStream) response.getEntity());
     assertEquals(EXPECTED.replace(ODataHttpMethod.GET.name(), ODataHttpMethod.PUT.name())
         .replace(Integer.toString(HttpStatusCodes.OK.getStatusCode()),
             Integer.toString(HttpStatusCodes.NO_CONTENT.getStatusCode()))
         .replace(HttpStatusCodes.OK.getInfo(), HttpStatusCodes.NO_CONTENT.getInfo()),
         actualJson);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    final String html = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(html.contains(HttpStatusCodes.NO_CONTENT.getInfo()));
   }
 
   @Test
@@ -125,32 +125,38 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     final ODataContext context = mockContext(ODataHttpMethod.GET);
     ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, "\"test\"", HttpContentType.APPLICATION_JSON);
 
-    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null)
-        .wrapResponse();
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
-    assertEquals(EXPECTED.replace("{\"request", "{\"body\":\"test\",\"request")
-        .replace("}}}",
-            "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"}}}"),
+    assertEquals(EXPECTED.replace("}}}",
+        "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"},"
+            + "\"body\":\"test\"}}"),
         entity);
 
     wrappedResponse = mockResponse(HttpStatusCodes.OK, "test", HttpContentType.TEXT_PLAIN);
-    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null)
-        .wrapResponse();
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
-    assertEquals(
-        EXPECTED.replace("{\"request", "{\"body\":\"test\",\"request")
-            .replace("}}}",
-                "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.TEXT_PLAIN + "\"}}}"),
+    assertEquals(EXPECTED.replace("}}}",
+        "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.TEXT_PLAIN + "\"},"
+            + "\"body\":\"test\"}}"),
         entity);
 
     wrappedResponse = mockResponse(HttpStatusCodes.OK, null, "image/png");
     when(wrappedResponse.getEntity()).thenReturn(new ByteArrayInputStream("test".getBytes()));
-    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null)
-        .wrapResponse();
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
-    assertEquals(EXPECTED.replace("{\"request", "{\"body\":\"dGVzdA==\",\"request")
-        .replace("}}}", "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"image/png\"}}}"),
+    assertEquals(EXPECTED.replace("}}}",
+        "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"image/png\"},"
+            + "\"body\":\"dGVzdA==\"}}"),
         entity);
+
+    when(wrappedResponse.getEntity()).thenReturn(new ByteArrayInputStream("test".getBytes()));
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("<img src=\"data:image/png;base64,dGVzdA==\" />"));
   }
 
   @Test
@@ -162,15 +168,39 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
 
     final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, HttpContentType.APPLICATION_JSON);
 
-    final ODataResponse response =
-        new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null)
-            .wrapResponse();
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
     assertEquals(EXPECTED.replace("},\"response",
         ",\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"}},\"response")
         .replace("}}}",
             "},\"headers\":{\"" + HttpHeaders.CONTENT_TYPE + "\":\"" + HttpContentType.APPLICATION_JSON + "\"}}}"),
         entity);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("<td class=\"name\">Content-Type</td><td class=\"value\">application/json</td>"));
+  }
+
+  @Test
+  public void server() throws Exception {
+    ODataContext context = mockContext(ODataHttpMethod.GET);
+    HttpServletRequest servletRequest = mock(HttpServletRequest.class);
+    when(servletRequest.getServerPort()).thenReturn(12345);
+    when(context.getParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT)).thenReturn(servletRequest);
+
+    final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, null);
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
+    String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertEquals(EXPECTED.replace("}}}", "}},\"server\":{\"environment\":{\"serverPort\":\"12345\"}}}"),
+        entity);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("<td class=\"name\">serverPort</td><td class=\"value\">12345</td>"));
   }
 
   @Test
@@ -201,17 +231,23 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     when(select2.getNavigationPropertySegments()).thenReturn(segments);
     when(uriInfo.getSelect()).thenReturn(Arrays.asList(select1, select2));
 
-    final ODataResponse response =
-        new ODataDebugResponseWrapper(context, wrappedResponse, uriInfo, null).wrapResponse();
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, uriInfo, null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
-    assertEquals(EXPECTED.replace("}}}",
-        "}},\"uri\":{\"filter\":{\"nodeType\":\"LITERAL\",\"type\":\"Edm.Boolean\",\"value\":\"true\"},"
-            + "\"orderby\":{\"nodeType\":\"order collection\","
-            + "\"orders\":[{\"nodeType\":\"ORDER\",\"sortorder\":\"asc\","
-            + "\"expression\":{\"nodeType\":\"LITERAL\",\"type\":\"Edm.Boolean\",\"value\":\"true\"}}]},"
-            + "\"expand/select\":{\"all\":false,\"properties\":[\"property\"],"
-            + "\"links\":[{\"nav\":{\"all\":false,\"properties\":[\"property\"],\"links\":[]}}]}}}"),
+    assertEquals(EXPECTED.replace("}}}", "}},\"server\":{"
+        + "\"uri\":{\"filter\":{\"nodeType\":\"LITERAL\",\"type\":\"Edm.Boolean\",\"value\":\"true\"},"
+        + "\"orderby\":{\"nodeType\":\"order collection\","
+        + "\"orders\":[{\"nodeType\":\"ORDER\",\"sortorder\":\"asc\","
+        + "\"expression\":{\"nodeType\":\"LITERAL\",\"type\":\"Edm.Boolean\",\"value\":\"true\"}}]},"
+        + "\"expandSelect\":{\"all\":false,\"properties\":[\"property\"],"
+        + "\"links\":[{\"nav\":{\"all\":false,\"properties\":[\"property\"],\"links\":[]}}]}}}}"),
         entity);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, uriInfo, null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("Edm.Boolean"));
+    assertTrue(entity.contains("asc"));
   }
 
   @Test
@@ -219,10 +255,6 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     final ODataContext context = mockContext(ODataHttpMethod.GET);
     final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, null);
 
-    UriInfo uriInfo = mock(UriInfo.class);
-    FilterExpression filter = mock(FilterExpression.class);
-    when(filter.getExpressionString()).thenReturn("wrong");
-    when(uriInfo.getFilter()).thenReturn(filter);
     ExpressionParserException exception = mock(ExpressionParserException.class);
     when(exception.getMessageReference()).thenReturn(ExpressionParserException.COMMON_ERROR);
     when(exception.getStackTrace()).thenReturn(new StackTraceElement[] {
@@ -231,17 +263,23 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     when(filterTree.getUriLiteral()).thenReturn("wrong");
     when(exception.getFilterTree()).thenReturn(filterTree);
 
-    final ODataResponse response =
-        new ODataDebugResponseWrapper(context, wrappedResponse, uriInfo, exception)
-            .wrapResponse();
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), exception,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
-    assertEquals(EXPECTED.replace("}}}",
-        "}},\"uri\":{\"error\":{\"filter\":\"wrong\"},\"filter\":null},"
-            + "\"stacktrace\":{\"exceptions\":[{\"class\":\"" + exception.getClass().getName() + "\","
-            + "\"message\":\"Error while parsing a ODATA expression.\","
-            + "\"invocation\":{\"class\":\"class\",\"method\":\"method\",\"line\":42}}],"
-            + "\"stacktrace\":[{\"class\":\"class\",\"method\":\"method\",\"line\":42}]}}"),
+    assertEquals(EXPECTED.replace("}}}", "}},\"server\":{"
+        + "\"uri\":{\"error\":{\"expression\":\"wrong\"}},"
+        + "\"stacktrace\":{\"exceptions\":[{\"class\":\"" + exception.getClass().getName() + "\","
+        + "\"message\":\"Error while parsing a ODATA expression.\","
+        + "\"invocation\":{\"class\":\"class\",\"method\":\"method\",\"line\":42}}],"
+        + "\"stacktrace\":[{\"class\":\"class\",\"method\":\"method\",\"line\":42}]}}}"),
         entity);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), exception,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("wrong"));
+    assertTrue(entity.contains(exception.getClass().getName()));
+    assertTrue(entity.contains("42"));
   }
 
   @Test
@@ -259,18 +297,22 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
 
     final ODataResponse wrappedResponse = mockResponse(HttpStatusCodes.OK, null, null);
 
-    ODataResponse response =
-        new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null)
-            .wrapResponse();
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
-    assertEquals(EXPECTED.replace("}}}",
-        "}},\"runtime\":[{\"class\":\"class\",\"method\":\"method\",\"duration\":41,\"memory\":3,"
-            + "\"children\":[{\"class\":\"class\",\"method\":\"inner\",\"duration\":8,\"memory\":6,\"children\":[]},"
-            + "{\"class\":\"class\",\"method\":\"inner\",\"duration\":3,\"memory\":3,\"children\":["
-            + "{\"class\":\"class\",\"method\":\"inner2\",\"duration\":1,\"memory\":3,\"children\":[]}]},"
-            + "{\"class\":\"class\",\"method\":\"child\",\"duration\":4,\"memory\":3,\"children\":[]}]},"
-            + "{\"class\":\"class\",\"method\":\"second\",\"duration\":54,\"memory\":3,\"children\":[]}]}"),
+    assertEquals(EXPECTED.replace("}}}", "}},\"server\":{"
+        + "\"runtime\":[{\"class\":\"class\",\"method\":\"method\",\"duration\":{\"value\":41,\"unit\":\"µs\"},"
+        + "\"children\":[{\"class\":\"class\",\"method\":\"inner\",\"duration\":{\"value\":8,\"unit\":\"µs\"}},"
+        + "{\"class\":\"class\",\"method\":\"inner\",\"duration\":{\"value\":3,\"unit\":\"µs\"},\"children\":["
+        + "{\"class\":\"class\",\"method\":\"inner2\",\"duration\":{\"value\":1,\"unit\":\"µs\"}}]},"
+        + "{\"class\":\"class\",\"method\":\"child\",\"duration\":{\"value\":4,\"unit\":\"µs\"}}]},"
+        + "{\"class\":\"class\",\"method\":\"second\",\"duration\":{\"value\":54,\"unit\":\"µs\"}}]}}"),
         entity);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), null,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("54&nbsp;&micro;s"));
   }
 
   @Test
@@ -290,22 +332,29 @@ public class ODataDebugResponseWrapperTest extends BaseTest {
     when(exception.getCause()).thenReturn(innerException);
     when(exception.getStackTrace()).thenReturn(Arrays.copyOfRange(stackTrace, 1, 3));
 
-    final ODataResponse response =
-        new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), exception)
-            .wrapResponse();
+    ODataResponse response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), exception,
+        ODataDebugResponseWrapper.ODATA_DEBUG_JSON).wrapResponse();
     String entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
     assertEquals(EXPECTED
         .replace(Integer.toString(HttpStatusCodes.OK.getStatusCode()),
             Integer.toString(HttpStatusCodes.BAD_REQUEST.getStatusCode()))
         .replace(HttpStatusCodes.OK.getInfo(), HttpStatusCodes.BAD_REQUEST.getInfo())
-        .replace("}}}", "}},"
+        .replace("}}}", "}},\"server\":{"
             + "\"stacktrace\":{\"exceptions\":[{\"class\":\"" + exception.getClass().getName() + "\","
             + "\"message\":\"Common exception\","
             + "\"invocation\":{\"class\":\"class\",\"method\":\"method\",\"line\":42}},"
             + "{\"class\":\"" + innerException.getClass().getName() + "\",\"message\":\"error\","
             + "\"invocation\":{\"class\":\"innerClass\",\"method\":\"innerMethod\",\"line\":99}}],"
             + "\"stacktrace\":[{\"class\":\"class\",\"method\":\"method\",\"line\":42},"
-            + "{\"class\":\"outerClass\",\"method\":\"outerMethod\",\"line\":11}]}}"),
+            + "{\"class\":\"outerClass\",\"method\":\"outerMethod\",\"line\":11}]}}}"),
         entity);
+
+    response = new ODataDebugResponseWrapper(context, wrappedResponse, mock(UriInfo.class), exception,
+        ODataDebugResponseWrapper.ODATA_DEBUG_HTML).wrapResponse();
+    entity = StringHelper.inputStreamToString((InputStream) response.getEntity());
+    assertTrue(entity.contains("Common exception"));
+    assertTrue(entity.contains("42"));
+    assertTrue(entity.contains("innerMethod"));
+    assertTrue(entity.contains("outerMethod"));
   }
 }