From 833b0a4adfb4a3b0412ce15affa88f80455c25d5 Mon Sep 17 00:00:00 2001
From: Brian Major <major.brian@gmail.com>
Date: Mon, 17 Aug 2015 15:46:53 -0700
Subject: [PATCH] ac2 - Added SyncOutput to wrap the http servlet response

---
 .../ca/nrc/cadc/ac/server/web/SyncOutput.java | 127 ++++++++++++++++++
 .../web/groups/AbstractGroupAction.java       |  32 ++---
 .../server/web/groups/CreateGroupAction.java  |   4 +-
 .../ac/server/web/groups/GetGroupAction.java  |   5 +-
 .../web/groups/GetGroupNamesAction.java       |   4 +-
 .../ac/server/web/groups/GroupServlet.java    |   4 +-
 .../server/web/groups/ModifyGroupAction.java  |   3 +-
 .../server/web/users/AbstractUserAction.java  |  41 +++---
 .../ac/server/web/users/CreateUserAction.java |   2 +-
 .../cadc/ac/server/web/users/UserServlet.java |   4 +-
 .../server/web/users/GetUserActionTest.java   |  29 ++--
 .../web/users/GetUserListActionTest.java      |  29 ++--
 .../web/users/ModifyUserActionTest.java       |  18 +--
 13 files changed, 215 insertions(+), 87 deletions(-)
 create mode 100644 projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java

diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java
new file mode 100644
index 00000000..9ebd42f7
--- /dev/null
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/SyncOutput.java
@@ -0,0 +1,127 @@
+/*
+************************************************************************
+*******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+**************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+*
+*  (c) 2011.                            (c) 2011.
+*  Government of Canada                 Gouvernement du Canada
+*  National Research Council            Conseil national de recherches
+*  Ottawa, Canada, K1A 0R6              Ottawa, Canada, K1A 0R6
+*  All rights reserved                  Tous droits réservés
+*
+*  NRC disclaims any warranties,        Le CNRC dénie toute garantie
+*  expressed, implied, or               énoncée, implicite ou légale,
+*  statutory, of any kind with          de quelque nature que ce
+*  respect to the software,             soit, concernant le logiciel,
+*  including without limitation         y compris sans restriction
+*  any warranty of merchantability      toute garantie de valeur
+*  or fitness for a particular          marchande ou de pertinence
+*  purpose. NRC shall not be            pour un usage particulier.
+*  liable in any event for any          Le CNRC ne pourra en aucun cas
+*  damages, whether direct or           être tenu responsable de tout
+*  indirect, special or general,        dommage, direct ou indirect,
+*  consequential or incidental,         particulier ou général,
+*  arising from the use of the          accessoire ou fortuit, résultant
+*  software.  Neither the name          de l'utilisation du logiciel. Ni
+*  of the National Research             le nom du Conseil National de
+*  Council of Canada nor the            Recherches du Canada ni les noms
+*  names of its contributors may        de ses  participants ne peuvent
+*  be used to endorse or promote        être utilisés pour approuver ou
+*  products derived from this           promouvoir les produits dérivés
+*  software without specific prior      de ce logiciel sans autorisation
+*  written permission.                  préalable et particulière
+*                                       par écrit.
+*
+*  This file is part of the             Ce fichier fait partie du projet
+*  OpenCADC project.                    OpenCADC.
+*
+*  OpenCADC is free software:           OpenCADC est un logiciel libre ;
+*  you can redistribute it and/or       vous pouvez le redistribuer ou le
+*  modify it under the terms of         modifier suivant les termes de
+*  the GNU Affero General Public        la “GNU Affero General Public
+*  License as published by the          License” telle que publiée
+*  Free Software Foundation,            par la Free Software Foundation
+*  either version 3 of the              : soit la version 3 de cette
+*  License, or (at your option)         licence, soit (à votre gré)
+*  any later version.                   toute version ultérieure.
+*
+*  OpenCADC is distributed in the       OpenCADC est distribué
+*  hope that it will be useful,         dans l’espoir qu’il vous
+*  but WITHOUT ANY WARRANTY;            sera utile, mais SANS AUCUNE
+*  without even the implied             GARANTIE : sans même la garantie
+*  warranty of MERCHANTABILITY          implicite de COMMERCIALISABILITÉ
+*  or FITNESS FOR A PARTICULAR          ni d’ADÉQUATION À UN OBJECTIF
+*  PURPOSE.  See the GNU Affero         PARTICULIER. Consultez la Licence
+*  General Public License for           Générale Publique GNU Affero
+*  more details.                        pour plus de détails.
+*
+*  You should have received             Vous devriez avoir reçu une
+*  a copy of the GNU Affero             copie de la Licence Générale
+*  General Public License along         Publique GNU Affero avec
+*  with OpenCADC.  If not, see          OpenCADC ; si ce n’est
+*  <http://www.gnu.org/licenses/>.      pas le cas, consultez :
+*                                       <http://www.gnu.org/licenses/>.
+*
+*  $Revision: 5 $
+*
+************************************************************************
+*/
+
+package ca.nrc.cadc.ac.server.web;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author majorb
+ */
+public class SyncOutput
+{
+    private static final Logger log = Logger.getLogger(SyncOutput.class);
+
+    protected HttpServletResponse response;
+    protected PrintWriter writer;
+
+    public SyncOutput(HttpServletResponse response)
+    {
+        this.response = response;
+    }
+
+    public boolean isOpen()
+    {
+        return (writer != null);
+    }
+
+    public void setCode(int code)
+    {
+        if (writer != null)
+            return;
+
+        response.setStatus(code);
+    }
+
+    public void setHeader(String key, Object value)
+    {
+        if (writer != null)
+            return;
+
+        if (value == null)
+            response.setHeader(key, null);
+        else
+            response.setHeader(key, value.toString());
+    }
+
+    public PrintWriter getWriter()
+        throws IOException
+    {
+        if (writer == null)
+        {
+            log.debug("opening writer");
+            writer = response.getWriter();
+        }
+        return writer;
+    }
+}
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/AbstractGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/AbstractGroupAction.java
index 94f05dc8..2805eda5 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/AbstractGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/AbstractGroupAction.java
@@ -89,6 +89,7 @@ import ca.nrc.cadc.ac.UserNotFoundException;
 import ca.nrc.cadc.ac.server.GroupPersistence;
 import ca.nrc.cadc.ac.server.PluginFactory;
 import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.net.TransientException;
 
 public abstract class AbstractGroupAction implements PrivilegedExceptionAction<Object>
@@ -96,7 +97,7 @@ public abstract class AbstractGroupAction implements PrivilegedExceptionAction<O
     private static final Logger log = Logger.getLogger(AbstractGroupAction.class);
     protected GroupLogInfo logInfo;
     protected HttpServletRequest request;
-    protected HttpServletResponse response;
+    protected SyncOutput syncOut;
 
     public AbstractGroupAction()
     {
@@ -114,9 +115,9 @@ public abstract class AbstractGroupAction implements PrivilegedExceptionAction<O
         this.request = request;
     }
 
-    void setHttpServletResponse(HttpServletResponse response)
+    void setSyncOut(SyncOutput syncOut)
     {
-        this.response = response;
+        this.syncOut = syncOut;
     }
 
     public Object run() throws PrivilegedActionException
@@ -206,26 +207,19 @@ public abstract class AbstractGroupAction implements PrivilegedExceptionAction<O
 
     private void sendError(int responseCode, String message)
     {
-        if (!this.response.isCommitted())
+        syncOut.setHeader("Content-Type", "text/plain");
+        if (message != null)
         {
-            this.response.setContentType("text/plain");
-            if (message != null)
+            try
             {
-                try
-                {
-                    this.response.getWriter().write(message);
-                }
-                catch (IOException e)
-                {
-                    log.warn("Could not write error message to output stream");
-                }
+                syncOut.getWriter() .write(message);
+            }
+            catch (IOException e)
+            {
+                log.warn("Could not write error message to output stream");
             }
-            this.response.setStatus(responseCode);
-        }
-        else
-        {
-            log.warn("Could not send error " + responseCode + " (" + message + ") because the response is already committed.");
         }
+        syncOut.setCode(responseCode);
     }
 
     <T extends Principal> GroupPersistence<T> getGroupPersistence()
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/CreateGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/CreateGroupAction.java
index e87f6c04..a4341c40 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/CreateGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/CreateGroupAction.java
@@ -94,9 +94,9 @@ public class CreateGroupAction extends AbstractGroupAction
         GroupReader groupReader = new GroupReader();
         Group group = groupReader.read(this.inputStream);
         Group newGroup = groupPersistence.addGroup(group);
-        this.response.setContentType("application/xml");
+        syncOut.setHeader("Content-Type", "application/xml");
         GroupWriter groupWriter = new GroupWriter();
-        groupWriter.write(newGroup, this.response.getOutputStream());
+        groupWriter.write(newGroup, syncOut.getWriter());
 
         List<String> addedMembers = null;
         if ((newGroup.getUserMembers().size() > 0) || (newGroup.getGroupMembers().size() > 0))
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupAction.java
index fba96b0a..99041186 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupAction.java
@@ -85,10 +85,9 @@ public class GetGroupAction extends AbstractGroupAction
     {
         GroupPersistence groupPersistence = getGroupPersistence();
         Group group = groupPersistence.getGroup(this.groupName);
-        this.response.setContentType("application/xml");
-
+        syncOut.setHeader("Content-Type", "application/xml");
         GroupWriter groupWriter = new GroupWriter();
-        groupWriter.write(group, this.response.getOutputStream());
+        groupWriter.write(group, syncOut.getWriter());
     }
 
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupNamesAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupNamesAction.java
index f03fadaf..ff26ddd8 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupNamesAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GetGroupNamesAction.java
@@ -91,9 +91,9 @@ public class GetGroupNamesAction extends AbstractGroupAction
         GroupPersistence groupPersistence = getGroupPersistence();
         Collection<String> groups = groupPersistence.getGroupNames();
         log.debug("Found " + groups.size() + " group names");
-        response.setContentType("text/plain");
+        syncOut.setHeader("Content-Type", "text/plain");
         log.debug("Set content-type to text/plain");
-        Writer writer = response.getWriter();
+        Writer writer = syncOut.getWriter();
         boolean start = true;
         for (final String group : groups)
         {
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java
index 15c647da..096b5827 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/GroupServlet.java
@@ -78,6 +78,7 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.log4j.Logger;
 
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.auth.AuthenticationUtil;
 import ca.nrc.cadc.util.StringUtil;
 
@@ -109,7 +110,8 @@ public class GroupServlet extends HttpServlet
 
             action.setLogInfo(logInfo);
             action.setHttpServletRequest(request);
-            action.setHttpServletResponse(response);
+            SyncOutput syncOut = new SyncOutput(response);
+            action.setSyncOut(syncOut);
 
             try
             {
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ModifyGroupAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ModifyGroupAction.java
index 4bd268ee..f4094251 100755
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ModifyGroupAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/groups/ModifyGroupAction.java
@@ -133,7 +133,8 @@ public class ModifyGroupAction extends AbstractGroupAction
         }
         logGroupInfo(group.getID(), deletedMembers, addedMembers);
 
-        this.response.sendRedirect(request);
+        syncOut.setHeader("Location", "/" + group.getID());
+        syncOut.setCode(303);
     }
 
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java
index 1c7f0e12..a4cd6240 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/AbstractUserAction.java
@@ -78,6 +78,7 @@ import ca.nrc.cadc.ac.json.JsonUserRequestReader;
 import ca.nrc.cadc.ac.json.JsonUserWriter;
 import ca.nrc.cadc.ac.server.PluginFactory;
 import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.auth.AuthenticationUtil;
 import ca.nrc.cadc.ac.xml.UserListWriter;
 import ca.nrc.cadc.ac.xml.UserReader;
@@ -104,7 +105,8 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
     static final String JSON_CONTENT_TYPE = "application/json";
 
     protected UserLogInfo logInfo;
-    protected HttpServletResponse response;
+    protected SyncOutput syncOut;
+
     protected String acceptedContentType = DEFAULT_CONTENT_TYPE;
 
     AbstractUserAction()
@@ -118,9 +120,9 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
         this.logInfo = logInfo;
     }
 
-    public void setResponse(HttpServletResponse response)
+    public void setSyncOut(SyncOutput syncOut)
     {
-        this.response = response;
+        this.syncOut = syncOut;
     }
 
     public Object run() throws IOException
@@ -182,21 +184,20 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
     }
 
     private void sendError(int responseCode, String message)
-        throws IOException
     {
-        if (!this.response.isCommitted())
+        syncOut.setHeader("Content-Type", "text/plain");
+        if (message != null)
         {
-            this.response.setContentType("text/plain");
-            if (message != null)
+            try
             {
-                this.response.getWriter().write(message);
+                syncOut.getWriter() .write(message);
+            }
+            catch (IOException e)
+            {
+                log.warn("Could not write error message to output stream");
             }
-            this.response.setStatus(responseCode);
-        }
-        else
-        {
-            log.warn("Could not send error " + responseCode + " (" + message + ") because the response is already committed.");
         }
+        syncOut.setCode(responseCode);
     }
 
     @SuppressWarnings("unchecked")
@@ -260,7 +261,7 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
     protected final User<Principal> readUser(final InputStream inputStream)
             throws IOException
     {
-        response.setContentType(acceptedContentType);
+        syncOut.setHeader("Content-Type", acceptedContentType);
         final User<Principal> user;
 
         if (acceptedContentType.equals(DEFAULT_CONTENT_TYPE))
@@ -292,8 +293,8 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
     protected final <T extends Principal> void writeUser(final User<T> user)
             throws IOException
     {
-        response.setContentType(acceptedContentType);
-        final Writer writer = response.getWriter();
+        syncOut.setHeader("Content-Type", acceptedContentType);
+        final Writer writer = syncOut.getWriter();
 
         if (acceptedContentType.equals(DEFAULT_CONTENT_TYPE))
         {
@@ -315,8 +316,8 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
     protected final void writeUsers(final Map<String, PersonalDetails> users)
             throws IOException
     {
-        response.setContentType(acceptedContentType);
-        final Writer writer = response.getWriter();
+        syncOut.setHeader("Content-Type", acceptedContentType);
+        final Writer writer = syncOut.getWriter();
 
         if (acceptedContentType.equals(DEFAULT_CONTENT_TYPE))
         {
@@ -350,8 +351,8 @@ public abstract class AbstractUserAction implements PrivilegedExceptionAction<Ob
             throw new IllegalStateException("No identities found.");
         }
 
-        response.setStatus(HttpServletResponse.SC_OK);
         final String redirectURL = "/" + id + "?idType=" + idType;
-        response.setHeader("Location", redirectURL);
+        syncOut.setHeader("Location", redirectURL);
+        syncOut.setCode(303);
     }
 }
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/CreateUserAction.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/CreateUserAction.java
index 82b6bcf6..aa40229c 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/CreateUserAction.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/CreateUserAction.java
@@ -95,7 +95,7 @@ public class CreateUserAction extends AbstractUserAction
         final UserRequest<Principal> userRequest = readUserRequest(this.inputStream);
         final User<Principal> newUser = userPersistence.addUser(userRequest);
 
-        response.setStatus(HttpServletResponse.SC_CREATED);
+        syncOut.setCode(201);
         logUserInfo(newUser.getUserID().getName());
     }
 
diff --git a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java
index 2f2ee879..42f4dbe3 100644
--- a/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java
+++ b/projects/cadcAccessControl-Server/src/ca/nrc/cadc/ac/server/web/users/UserServlet.java
@@ -79,6 +79,7 @@ import javax.servlet.http.HttpServletResponse;
 import ca.nrc.cadc.util.StringUtil;
 import org.apache.log4j.Logger;
 
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.auth.AuthenticationUtil;
 
 public class UserServlet extends HttpServlet
@@ -103,9 +104,10 @@ public class UserServlet extends HttpServlet
             logInfo.setSubject(subject);
 
             AbstractUserAction action = factory.createAction(request);
+            SyncOutput syncOut = new SyncOutput(response);
 
             action.setLogInfo(logInfo);
-            action.setResponse(response);
+            action.setSyncOut(syncOut);
             action.setAcceptedContentType(getAcceptedContentType(request));
 
             try
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserActionTest.java
index 565a09d3..8fbc0a4a 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserActionTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserActionTest.java
@@ -70,6 +70,7 @@ package ca.nrc.cadc.ac.server.web.users;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.json.JsonUserWriter;
 import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.ac.xml.UserWriter;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import org.junit.Test;
@@ -87,8 +88,8 @@ public class GetUserActionTest
     @Test
     public void writeUserXML() throws Exception
     {
-        final HttpServletResponse mockResponse =
-                createMock(HttpServletResponse.class);
+        final SyncOutput mockSyncOut =
+                createMock(SyncOutput.class);
         final UserPersistence<HttpPrincipal> mockUserPersistence =
                 createMock(UserPersistence.class);
         final HttpPrincipal userID = new HttpPrincipal("CADCtest");
@@ -107,13 +108,13 @@ public class GetUserActionTest
         final PrintWriter printWriter = new PrintWriter(writer);
 
         expect(mockUserPersistence.getUser(userID)).andReturn(user).once();
-        expect(mockResponse.getWriter()).andReturn(printWriter).once();
-        mockResponse.setContentType("text/xml");
+        expect(mockSyncOut.getWriter()).andReturn(printWriter).once();
+        mockSyncOut.setHeader("Content-Type", "text/xml");
         expectLastCall().once();
 
-        replay(mockResponse, mockUserPersistence);
+        replay(mockSyncOut, mockUserPersistence);
 
-        testSubject.setResponse(mockResponse);
+        testSubject.setSyncOut(mockSyncOut);
         testSubject.doAction();
 
         StringBuilder sb = new StringBuilder();
@@ -121,14 +122,14 @@ public class GetUserActionTest
         userWriter.write(user, sb);
         assertEquals(sb.toString(), writer.toString());
 
-        verify(mockResponse, mockUserPersistence);
+        verify(mockSyncOut, mockUserPersistence);
     }
 
     @Test
     public void writeUserJSON() throws Exception
     {
-        final HttpServletResponse mockResponse =
-                createMock(HttpServletResponse.class);
+        final SyncOutput mockSyncOut =
+                createMock(SyncOutput.class);
         final UserPersistence<HttpPrincipal> mockUserPersistence =
                 createMock(UserPersistence.class);
         final HttpPrincipal userID = new HttpPrincipal("CADCtest");
@@ -149,12 +150,12 @@ public class GetUserActionTest
         final PrintWriter printWriter = new PrintWriter(writer);
 
         expect(mockUserPersistence.getUser(userID)).andReturn(user).once();
-        expect(mockResponse.getWriter()).andReturn(printWriter).once();
-        mockResponse.setContentType("application/json");
+        expect(mockSyncOut.getWriter()).andReturn(printWriter).once();
+        mockSyncOut.setHeader("Content-Type", "application/json");
         expectLastCall().once();
 
-        replay(mockResponse, mockUserPersistence);
-        testSubject.setResponse(mockResponse);
+        replay(mockSyncOut, mockUserPersistence);
+        testSubject.setSyncOut(mockSyncOut);
         UserLogInfo logInfo = createMock(UserLogInfo.class);
         testSubject.setLogInfo(logInfo);
         testSubject.doAction();
@@ -164,6 +165,6 @@ public class GetUserActionTest
         userWriter.write(user, sb);
         assertEquals(sb.toString(), writer.toString());
 
-        verify(mockResponse, mockUserPersistence);
+        verify(mockSyncOut, mockUserPersistence);
     }
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserListActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserListActionTest.java
index 19e83893..8e3a6db4 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserListActionTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/GetUserListActionTest.java
@@ -72,6 +72,7 @@ package ca.nrc.cadc.ac.server.web.users;
 import ca.nrc.cadc.ac.PersonalDetails;
 import ca.nrc.cadc.ac.json.JsonUserListWriter;
 import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.ac.xml.UserListWriter;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import org.apache.log4j.Level;
@@ -110,8 +111,8 @@ public class GetUserListActionTest
     @SuppressWarnings("unchecked")
     public void testWriteUsersJSON() throws Exception
     {
-        final HttpServletResponse mockResponse =
-                createMock(HttpServletResponse.class);
+        final SyncOutput mockSyncOut =
+                createMock(SyncOutput.class);
         final UserPersistence<HttpPrincipal> mockUserPersistence =
                 createMock(UserPersistence.class);
         final Map<String, PersonalDetails> userEntries =
@@ -139,12 +140,12 @@ public class GetUserListActionTest
 
         expect(mockUserPersistence.getUsers()).andReturn(
                 userEntries).once();
-        expect(mockResponse.getWriter()).andReturn(actualPrintWriter).once();
-        mockResponse.setContentType("application/json");
+        expect(mockSyncOut.getWriter()).andReturn(actualPrintWriter).once();
+        mockSyncOut.setHeader("Content-Type", "application/json");
         expectLastCall().once();
 
-        replay(mockResponse, mockUserPersistence);
-        testSubject.setResponse(mockResponse);
+        replay(mockSyncOut, mockUserPersistence);
+        testSubject.setSyncOut(mockSyncOut);
         UserLogInfo logInfo = createMock(UserLogInfo.class);
         testSubject.setLogInfo(logInfo);
         testSubject.doAction();
@@ -155,15 +156,15 @@ public class GetUserListActionTest
         userListWriter.write(userEntries, expectedPrintWriter);
         JSONAssert.assertEquals(expectedWriter.toString(), actualWriter.toString(), false);
 
-        verify(mockResponse, mockUserPersistence);
+        verify(mockSyncOut, mockUserPersistence);
     }
 
     @Test
     @SuppressWarnings("unchecked")
     public void testWriteUsersXML() throws Exception
     {
-        final HttpServletResponse mockResponse =
-                createMock(HttpServletResponse.class);
+        final SyncOutput mockSyncOut =
+                createMock(SyncOutput.class);
         final UserPersistence<HttpPrincipal> mockUserPersistence =
                 createMock(UserPersistence.class);
         final Map<String, PersonalDetails> userEntries =
@@ -189,12 +190,12 @@ public class GetUserListActionTest
 
         expect(mockUserPersistence.getUsers()).andReturn(
                 userEntries).once();
-        expect(mockResponse.getWriter()).andReturn(actualPrintWriter).once();
-        mockResponse.setContentType("text/xml");
+        expect(mockSyncOut.getWriter()).andReturn(actualPrintWriter).once();
+        mockSyncOut.setHeader("Content-Type", "text/xml");
         expectLastCall().once();
 
-        replay(mockResponse, mockUserPersistence);
-        testSubject.setResponse(mockResponse);
+        replay(mockSyncOut, mockUserPersistence);
+        testSubject.setSyncOut(mockSyncOut);
         UserLogInfo logInfo = createMock(UserLogInfo.class);
         testSubject.setLogInfo(logInfo);
         testSubject.doAction();
@@ -205,6 +206,6 @@ public class GetUserListActionTest
         userListWriter.write(userEntries, expectedPrintWriter);
         assertEquals("Wrong XML", expectedWriter.toString(), actualWriter.toString());
 
-        verify(mockResponse, mockUserPersistence);
+        verify(mockSyncOut, mockUserPersistence);
     }
 }
diff --git a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java
index 00bfed3f..ff4538b2 100644
--- a/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java
+++ b/projects/cadcAccessControl-Server/test/src/ca/nrc/cadc/ac/server/web/users/ModifyUserActionTest.java
@@ -72,6 +72,7 @@ import ca.nrc.cadc.ac.PersonalDetails;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.json.JsonUserWriter;
 import ca.nrc.cadc.ac.server.UserPersistence;
+import ca.nrc.cadc.ac.server.web.SyncOutput;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import org.junit.Test;
 
@@ -120,8 +121,8 @@ public class ModifyUserActionTest
 
         final HttpServletRequest mockRequest =
                 createMock(HttpServletRequest.class);
-        final HttpServletResponse mockResponse =
-                createMock(HttpServletResponse.class);
+        final SyncOutput mockSyncOut =
+                createMock(SyncOutput.class);
 
         @SuppressWarnings("unchecked")
         final UserPersistence<Principal> mockUserPersistence =
@@ -133,17 +134,16 @@ public class ModifyUserActionTest
 //        expect(mockRequest.getRemoteAddr()).andReturn(requestURL).
 //                once();
 
-        mockResponse.setHeader("Location", "/CADCtest?idType=http");
+        mockSyncOut.setHeader("Location", "/CADCtest?idType=http");
         expectLastCall().once();
 
-        mockResponse.setStatus(200);
-
+        mockSyncOut.setCode(303);
         expectLastCall().once();
 
-        mockResponse.setContentType("application/json");
+        mockSyncOut.setHeader("Content-Type", "application/json");
         expectLastCall().once();
 
-        replay(mockRequest, mockResponse, mockUserPersistence);
+        replay(mockRequest, mockSyncOut, mockUserPersistence);
 
         final ModifyUserAction testSubject = new ModifyUserAction(inputStream)
         {
@@ -156,11 +156,11 @@ public class ModifyUserActionTest
         };
 
         testSubject.setAcceptedContentType("application/json");
-        testSubject.response = mockResponse;
+        testSubject.syncOut = mockSyncOut;
         UserLogInfo logInfo = createMock(UserLogInfo.class);
         testSubject.setLogInfo(logInfo);
         testSubject.doAction();
 
-        verify(mockRequest, mockResponse, mockUserPersistence);
+        verify(mockRequest, mockSyncOut, mockUserPersistence);
     }
 }
-- 
GitLab