From fa920a52af2a6a29f91da73162f252666e7ad48f Mon Sep 17 00:00:00 2001
From: "Endi S. Dewata" <edewata@redhat.com>
Date: Mon, 10 Feb 2014 13:56:15 -0500
Subject: [PATCH] Added CLI parameter to select message format.

A new CLI parameter has been added to allow the user select the
REST message format. This is done by setting the default consumes
and produces when creating the client proxy. For this to work the
hard-coded @Consumes and @Produces annotations need to be removed
from the interface definition. A new interceptor has been added
to validate the message format before executing the operation.

Ticket #554
---
 .../dogtagpki/server/ca/rest/CAApplication.java    |  2 +
 .../com/netscape/certsrv/base/PKIException.java    |  5 ++
 .../com/netscape/certsrv/client/ClientConfig.java  | 16 +++++
 .../src/com/netscape/certsrv/client/PKIClient.java |  2 +
 .../com/netscape/certsrv/client/PKIConnection.java | 17 ++++-
 .../src/com/netscape/cmstools/cli/MainCLI.java     |  8 +++
 .../dogtagpki/server/kra/rest/KRAApplication.java  |  2 +
 .../server/ocsp/rest/OCSPApplication.java          |  2 +
 .../com/netscape/cms/servlet/base/PKIService.java  | 25 ++++++++
 .../server/rest/MessageFormatInterceptor.java      | 73 ++++++++++++++++++++++
 .../dogtagpki/server/tks/rest/TKSApplication.java  |  2 +
 .../dogtagpki/server/tps/rest/TPSApplication.java  |  2 +
 12 files changed, 155 insertions(+), 1 deletion(-)
 create mode 100644 base/server/cms/src/org/dogtagpki/server/rest/MessageFormatInterceptor.java

diff --git a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
index 78f3da941d0b5f33737a9b4941a6d945e2fbe150..2e193d5e118bd4204dc3a228cba0dbef4bc86272 100644
--- a/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
+++ b/base/ca/src/org/dogtagpki/server/ca/rest/CAApplication.java
@@ -10,6 +10,7 @@ import org.dogtagpki.server.rest.AccountService;
 import org.dogtagpki.server.rest.AuditService;
 import org.dogtagpki.server.rest.AuthMethodInterceptor;
 import org.dogtagpki.server.rest.GroupService;
+import org.dogtagpki.server.rest.MessageFormatInterceptor;
 import org.dogtagpki.server.rest.SecurityDomainService;
 import org.dogtagpki.server.rest.SelfTestService;
 import org.dogtagpki.server.rest.SystemCertService;
@@ -91,6 +92,7 @@ public class CAApplication extends Application {
         // interceptors
         singletons.add(new AuthMethodInterceptor());
         singletons.add(new ACLInterceptor());
+        singletons.add(new MessageFormatInterceptor());
     }
 
     public Set<Class<?>> getClasses() {
diff --git a/base/common/src/com/netscape/certsrv/base/PKIException.java b/base/common/src/com/netscape/certsrv/base/PKIException.java
index ad55d139e1353cb2c7cdb3d8412ebe75ad009ae6..f89c1b18ce192226fa137d82ae107dbf074f1b32 100644
--- a/base/common/src/com/netscape/certsrv/base/PKIException.java
+++ b/base/common/src/com/netscape/certsrv/base/PKIException.java
@@ -31,6 +31,11 @@ public class PKIException extends RuntimeException {
 
     public int code;
 
+    public PKIException(Response.Status status) {
+        super(status.getReasonPhrase());
+        code = status.getStatusCode();
+    }
+
     public PKIException(String message) {
         super(message);
         code = Response.Status.INTERNAL_SERVER_ERROR.getStatusCode();
diff --git a/base/common/src/com/netscape/certsrv/client/ClientConfig.java b/base/common/src/com/netscape/certsrv/client/ClientConfig.java
index c85db52d3b0ea8dd13d451e56b7c45d38b855126..da4e3d780f1aa01a88061ed9af868cb4097b63a5 100644
--- a/base/common/src/com/netscape/certsrv/client/ClientConfig.java
+++ b/base/common/src/com/netscape/certsrv/client/ClientConfig.java
@@ -55,6 +55,7 @@ public class ClientConfig {
     String certPassword;
     String username;
     String password;
+    String messageFormat;
 
     @XmlElement(name="ServerURI")
     public URI getServerURI() {
@@ -123,6 +124,15 @@ public class ClientConfig {
         this.password = password;
     }
 
+    @XmlElement(name="MessageFormat")
+    public String getMessageFormat() {
+        return messageFormat;
+    }
+
+    public void setMessageFormat(String messageFormat) {
+        this.messageFormat = messageFormat;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -130,6 +140,7 @@ public class ClientConfig {
         result = prime * result + ((certDatabase == null) ? 0 : certDatabase.hashCode());
         result = prime * result + ((certNickname == null) ? 0 : certNickname.hashCode());
         result = prime * result + ((certPassword == null) ? 0 : certPassword.hashCode());
+        result = prime * result + ((messageFormat == null) ? 0 : messageFormat.hashCode());
         result = prime * result + ((password == null) ? 0 : password.hashCode());
         result = prime * result + ((serverURI == null) ? 0 : serverURI.hashCode());
         result = prime * result + ((username == null) ? 0 : username.hashCode());
@@ -160,6 +171,11 @@ public class ClientConfig {
                 return false;
         } else if (!certPassword.equals(other.certPassword))
             return false;
+        if (messageFormat == null) {
+            if (other.messageFormat != null)
+                return false;
+        } else if (!messageFormat.equals(other.messageFormat))
+            return false;
         if (password == null) {
             if (other.password != null)
                 return false;
diff --git a/base/common/src/com/netscape/certsrv/client/PKIClient.java b/base/common/src/com/netscape/certsrv/client/PKIClient.java
index 2b455fa8c893cd1941f0e29256825cafc1575007..4f5a98d25b12246936ee9a0d3451ef2b55197cba 100644
--- a/base/common/src/com/netscape/certsrv/client/PKIClient.java
+++ b/base/common/src/com/netscape/certsrv/client/PKIClient.java
@@ -33,6 +33,8 @@ import com.netscape.cmsutil.util.Utils;
 
 public class PKIClient {
 
+    public final static String[] MESSAGE_FORMATS = { "xml", "json" };
+
     public ClientConfig config;
     public PKIConnection connection;
 
diff --git a/base/common/src/com/netscape/certsrv/client/PKIConnection.java b/base/common/src/com/netscape/certsrv/client/PKIConnection.java
index 1c3e58a1be5d4c6a679c39e7e01547324f86724a..06c51aaad51ec89625eb07280bda28fc2caf7f08 100644
--- a/base/common/src/com/netscape/certsrv/client/PKIConnection.java
+++ b/base/common/src/com/netscape/certsrv/client/PKIConnection.java
@@ -17,12 +17,14 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
 
 import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 
@@ -511,7 +513,20 @@ public class PKIConnection {
 
     public <T> T createProxy(URI uri, Class<T> clazz) throws URISyntaxException {
         ResteasyWebTarget target = resteasyClient.target(uri);
-        return ProxyBuilder.builder(clazz, target).build();
+        ProxyBuilder<T> builder = ProxyBuilder.builder(clazz, target);
+
+        String messageFormat = config.getMessageFormat();
+        if (messageFormat == null) messageFormat = PKIClient.MESSAGE_FORMATS[0];
+
+        if (!Arrays.asList(PKIClient.MESSAGE_FORMATS).contains(messageFormat)) {
+            throw new Error("Unsupported message format: " + messageFormat);
+        }
+
+        MediaType contentType = MediaType.valueOf("application/" + messageFormat);
+        builder.defaultConsumes(contentType);
+        builder.defaultProduces(contentType);
+
+        return builder.build();
     }
 
     public <T> T getEntity(Response response, Class<T> clazz) {
diff --git a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
index 082c8140b9f482a579b95a0afce747de2eb97272..3527238d203964c414f0b04e6bfd8fce922852ba 100644
--- a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
+++ b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java
@@ -185,6 +185,10 @@ public class MainCLI extends CLI {
         option.setArgName("list");
         options.addOption(option);
 
+        option = new Option(null, "message-format", true, "Message format: xml (default), json");
+        option.setArgName("format");
+        options.addOption(option);
+
         options.addOption("v", false, "Verbose");
         options.addOption(null, "help", false, "Help");
         options.addOption(null, "version", false, "Version");
@@ -252,6 +256,10 @@ public class MainCLI extends CLI {
         }
 
         if (verbose) System.out.println("Security database: "+this.certDatabase.getAbsolutePath());
+
+        String messageFormat = cmd.getOptionValue("message-format");
+        config.setMessageFormat(messageFormat);
+        if (verbose) System.out.println("Message format: " + messageFormat);
     }
 
     public void convertCertStatusList(String list, Collection<Integer> statuses) throws Exception {
diff --git a/base/kra/src/org/dogtagpki/server/kra/rest/KRAApplication.java b/base/kra/src/org/dogtagpki/server/kra/rest/KRAApplication.java
index ea8a6c0389c36934d13b7bc40755b7a15c6d77f9..c8dc553e209d20e396a17927d1f75a21b2648321 100644
--- a/base/kra/src/org/dogtagpki/server/kra/rest/KRAApplication.java
+++ b/base/kra/src/org/dogtagpki/server/kra/rest/KRAApplication.java
@@ -10,6 +10,7 @@ import org.dogtagpki.server.rest.AccountService;
 import org.dogtagpki.server.rest.AuditService;
 import org.dogtagpki.server.rest.AuthMethodInterceptor;
 import org.dogtagpki.server.rest.GroupService;
+import org.dogtagpki.server.rest.MessageFormatInterceptor;
 import org.dogtagpki.server.rest.SecurityDomainService;
 import org.dogtagpki.server.rest.SelfTestService;
 import org.dogtagpki.server.rest.SystemCertService;
@@ -69,6 +70,7 @@ public class KRAApplication extends Application {
         // interceptors
         singletons.add(new AuthMethodInterceptor());
         singletons.add(new ACLInterceptor());
+        singletons.add(new MessageFormatInterceptor());
     }
 
     public Set<Class<?>> getClasses() {
diff --git a/base/ocsp/src/org/dogtagpki/server/ocsp/rest/OCSPApplication.java b/base/ocsp/src/org/dogtagpki/server/ocsp/rest/OCSPApplication.java
index 1be534978ff967e5be9d5d7c067b649c247e8b91..688fec7728122994efb63cd0e85b91435a2652cf 100644
--- a/base/ocsp/src/org/dogtagpki/server/ocsp/rest/OCSPApplication.java
+++ b/base/ocsp/src/org/dogtagpki/server/ocsp/rest/OCSPApplication.java
@@ -10,6 +10,7 @@ import org.dogtagpki.server.rest.AccountService;
 import org.dogtagpki.server.rest.AuditService;
 import org.dogtagpki.server.rest.AuthMethodInterceptor;
 import org.dogtagpki.server.rest.GroupService;
+import org.dogtagpki.server.rest.MessageFormatInterceptor;
 import org.dogtagpki.server.rest.SecurityDomainService;
 import org.dogtagpki.server.rest.SelfTestService;
 import org.dogtagpki.server.rest.SystemCertService;
@@ -65,6 +66,7 @@ public class OCSPApplication extends Application {
         // interceptors
         singletons.add(new AuthMethodInterceptor());
         singletons.add(new ACLInterceptor());
+        singletons.add(new MessageFormatInterceptor());
     }
 
     public Set<Class<?>> getClasses() {
diff --git a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java
index 6550715f581666551ec1b9807b816a1b576b12d4..8544efb486bb0da048ae5f4ca53807f70bfe78df 100644
--- a/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java
+++ b/base/server/cms/src/com/netscape/cms/servlet/base/PKIService.java
@@ -20,6 +20,7 @@ package com.netscape.cms.servlet.base;
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.security.cert.CertificateEncodingException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -27,13 +28,16 @@ import java.util.Map;
 
 import javax.ws.rs.FormParam;
 import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.Context;
 import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Request;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 
 import com.netscape.certsrv.apps.CMS;
+import com.netscape.certsrv.base.PKIException;
 import com.netscape.certsrv.cert.CertData;
 import com.netscape.certsrv.logging.IAuditor;
 import com.netscape.certsrv.logging.ILogger;
@@ -52,18 +56,37 @@ public class PKIService {
     // caching parameters
     public static final int DEFAULT_LONG_CACHE_LIFETIME = 1000;
 
+    public static List<MediaType> MESSAGE_FORMATS = Arrays.asList(
+            MediaType.APPLICATION_XML_TYPE,
+            MediaType.APPLICATION_JSON_TYPE
+    );
+
+    @Context
+    private HttpHeaders headers;
+
     public ILogger logger = CMS.getLogger();
     public IAuditor auditor = CMS.getAuditor();
 
+    public MediaType getMessageFormat() {
+
+        for (MediaType acceptableFormat : headers.getAcceptableMediaTypes()) {
+            if (MESSAGE_FORMATS.contains(acceptableFormat)) return acceptableFormat;
+        }
+
+        throw new PKIException(Response.Status.NOT_ACCEPTABLE);
+    }
+
     public Response createOKResponse() {
         return Response
                 .ok()
+                .type(getMessageFormat())
                 .build();
     }
 
     public Response createOKResponse(Object entity) {
         return Response
                 .ok(entity)
+                .type(getMessageFormat())
                 .build();
     }
 
@@ -71,12 +94,14 @@ public class PKIService {
         return Response
                 .created(link)
                 .entity(entity)
+                .type(getMessageFormat())
                 .build();
     }
 
     public Response createNoContentResponse() {
         return Response
                 .noContent()
+                .type(getMessageFormat())
                 .build();
     }
 
diff --git a/base/server/cms/src/org/dogtagpki/server/rest/MessageFormatInterceptor.java b/base/server/cms/src/org/dogtagpki/server/rest/MessageFormatInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..f932245963295870323f00c11b35f4b82bd6833b
--- /dev/null
+++ b/base/server/cms/src/org/dogtagpki/server/rest/MessageFormatInterceptor.java
@@ -0,0 +1,73 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program is distributed in the hope that it will be useful,
+//but WITHOUT ANY WARRANTY; without even the implied warranty of
+//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//GNU General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2014 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+package org.dogtagpki.server.rest;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import org.jboss.resteasy.core.ResourceMethodInvoker;
+
+import com.netscape.certsrv.apps.CMS;
+import com.netscape.cms.servlet.base.PKIService;
+
+/**
+ * @author Endi S. Dewata
+ */
+@Provider
+public class MessageFormatInterceptor implements ContainerRequestFilter {
+
+    @Override
+    public void filter(ContainerRequestContext requestContext) throws IOException {
+        ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext
+                .getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
+        Method method = methodInvoker.getMethod();
+        Class<?> clazz = methodInvoker.getResourceClass();
+
+        CMS.debug("MessageFormatInterceptor: " + clazz.getSimpleName() + "." + method.getName() + "()");
+
+        MediaType requestFormat = requestContext.getMediaType();
+        if (!PKIService.MESSAGE_FORMATS.contains(requestFormat)) {
+            CMS.debug("MessageFormatInterceptor: request format " + requestFormat + " is not supported");
+            throw new WebApplicationException(Response.Status.UNSUPPORTED_MEDIA_TYPE);
+        }
+
+        CMS.debug("MessageFormatInterceptor: request format " + requestFormat + " is supported");
+
+        MediaType responseFormat = null;
+        for (MediaType format : requestContext.getAcceptableMediaTypes()) {
+            if (PKIService.MESSAGE_FORMATS.contains(format)) {
+                responseFormat = format;
+                break;
+            }
+            CMS.debug("MessageFormatInterceptor: response format " + format + " is not supported");
+        }
+
+        if (responseFormat == null) {
+            throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE);
+        }
+
+        CMS.debug("MessageFormatInterceptor: response format " + responseFormat + " is supported");
+    }
+}
diff --git a/base/tks/src/org/dogtagpki/server/tks/rest/TKSApplication.java b/base/tks/src/org/dogtagpki/server/tks/rest/TKSApplication.java
index 976b9bd9faef6d441670a87bb321a8236241c649..f824f90889b8c8b3d92e1ae9f416af82111fc74d 100644
--- a/base/tks/src/org/dogtagpki/server/tks/rest/TKSApplication.java
+++ b/base/tks/src/org/dogtagpki/server/tks/rest/TKSApplication.java
@@ -10,6 +10,7 @@ import org.dogtagpki.server.rest.AccountService;
 import org.dogtagpki.server.rest.AuditService;
 import org.dogtagpki.server.rest.AuthMethodInterceptor;
 import org.dogtagpki.server.rest.GroupService;
+import org.dogtagpki.server.rest.MessageFormatInterceptor;
 import org.dogtagpki.server.rest.SelfTestService;
 import org.dogtagpki.server.rest.SystemCertService;
 import org.dogtagpki.server.rest.SystemConfigService;
@@ -52,6 +53,7 @@ public class TKSApplication extends Application {
         // interceptors
         singletons.add(new AuthMethodInterceptor());
         singletons.add(new ACLInterceptor());
+        singletons.add(new MessageFormatInterceptor());
     }
 
     public Set<Class<?>> getClasses() {
diff --git a/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/TPSApplication.java b/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/TPSApplication.java
index 0dfd2b7c097e85bfaa336449c6433afe3f8b024f..7ecadb5a798de9442669bfcffb077eaf1554564e 100644
--- a/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/TPSApplication.java
+++ b/base/tps-tomcat/src/org/dogtagpki/server/tps/rest/TPSApplication.java
@@ -27,6 +27,7 @@ import org.dogtagpki.server.rest.AccountService;
 import org.dogtagpki.server.rest.AuditService;
 import org.dogtagpki.server.rest.AuthMethodInterceptor;
 import org.dogtagpki.server.rest.GroupService;
+import org.dogtagpki.server.rest.MessageFormatInterceptor;
 import org.dogtagpki.server.rest.SelfTestService;
 import org.dogtagpki.server.rest.SystemCertService;
 import org.dogtagpki.server.rest.SystemConfigService;
@@ -92,6 +93,7 @@ public class TPSApplication extends Application {
         // interceptors
         singletons.add(new AuthMethodInterceptor());
         singletons.add(new ACLInterceptor());
+        singletons.add(new MessageFormatInterceptor());
     }
 
     public Set<Class<?>> getClasses() {
-- 
1.8.4.2

