From 27cb2f56b8b24afe218f8b166b5df9858dcc06be Mon Sep 17 00:00:00 2001
From: Jeff Burke <Jeff.Burke@nrc-cnrc.gc.ca>
Date: Wed, 15 Jul 2015 07:51:57 -0700
Subject: [PATCH] s1734: added User and Group JSON reader/writers

---
 projects/cadcAccessControl/build.xml          |   1 +
 .../src/ca/nrc/cadc/ac/json/GroupReader.java  | 278 ++++++++++++++++++
 .../src/ca/nrc/cadc/ac/json/GroupWriter.java  | 257 ++++++++++++++++
 .../src/ca/nrc/cadc/ac/json/UserReader.java   |  40 ++-
 .../src/ca/nrc/cadc/ac/json/UserWriter.java   |   4 +-
 .../cadc/ac/json/GroupReaderWriterTest.java   | 189 ++++++++++++
 .../cadc/ac/json/UserReaderWriterTest.java    |   4 +-
 .../ac/xml/GroupPropertyReaderWriterTest.java |  14 +-
 .../cadc/ac/xml/GroupReaderWriterTest.java    |  45 ++-
 .../cadc/ac/xml/GroupsReaderWriterTest.java   |  21 +-
 .../cadc/ac/xml/IdentityReaderWriterTest.java |  40 +--
 .../nrc/cadc/ac/xml/UserReaderWriterTest.java |  21 +-
 12 files changed, 821 insertions(+), 93 deletions(-)
 create mode 100755 projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupReader.java
 create mode 100755 projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupWriter.java
 create mode 100644 projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/GroupReaderWriterTest.java

diff --git a/projects/cadcAccessControl/build.xml b/projects/cadcAccessControl/build.xml
index 7dab6cbb..3296ce95 100644
--- a/projects/cadcAccessControl/build.xml
+++ b/projects/cadcAccessControl/build.xml
@@ -129,6 +129,7 @@
             <test name="ca.nrc.cadc.ac.json.UserDetailsReaderWriterTest" />
             <test name="ca.nrc.cadc.ac.json.IdentityReaderWriterTest" />
             <test name="ca.nrc.cadc.ac.json.UserReaderWriterTest" />
+            <test name="ca.nrc.cadc.ac.json.GroupReaderWriterTest" />
             <formatter type="plain" usefile="false" />
         </junit>
     </target>
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupReader.java
new file mode 100755
index 00000000..f726b87a
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupReader.java
@@ -0,0 +1,278 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  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: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.json;
+
+import ca.nrc.cadc.ac.AC;
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.ReaderException;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.date.DateUtil;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URISyntaxException;
+import java.security.Principal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Scanner;
+
+public class GroupReader
+{
+    /**
+     * Construct a Group from a InputStream.
+     *
+     * @param in InputStream.
+     * @return Group Group.
+     * @throws ReaderException
+     * @throws IOException
+     * @throws URISyntaxException
+     */
+    public static Group read(InputStream in)
+        throws ReaderException, IOException
+    {
+        if (in == null)
+        {
+            throw new IOException("stream closed");
+        }
+        InputStreamReader reader;
+
+        Scanner s = new Scanner(in).useDelimiter("\\A");
+        String json = s.hasNext() ? s.next() : "";
+
+        return read(json);
+    }
+
+    /**
+     * Construct a Group from a Reader.
+     *
+     * @param reader Reader.
+     * @return Group Group.
+     * @throws ReaderException
+     * @throws IOException
+     * @throws URISyntaxException
+     */
+    public static Group read(Reader reader)
+        throws ReaderException, IOException
+    {
+        if (reader == null)
+        {
+            throw new IllegalArgumentException("reader must not be null");
+        }
+
+        Scanner s = new Scanner(reader).useDelimiter("\\A");
+        String json = s.hasNext() ? s.next() : "";
+
+        return read(json);
+    }
+
+    /**
+     * Construct a Group from an JSON String source.
+     *
+     * @param json String of JSON.
+     * @return Group Group.
+     * @throws ReaderException
+     * @throws IOException
+     * @throws URISyntaxException
+     */
+    public static Group read(String json)
+        throws ReaderException, IOException
+    {
+        if (json == null)
+        {
+            throw new IllegalArgumentException("JSON must not be null");
+        }
+
+        // Create a JSONObject from the JSON
+        try
+        {
+            return parseGroup(new JSONObject(json).getJSONObject("group"));
+        }
+        catch (JSONException e)
+        {
+            String error = "Unable to parse JSON to Group because " +
+                           e.getMessage();
+            throw new ReaderException(error, e);
+        }
+    }
+
+    protected static Group parseGroup(JSONObject groupObject)
+        throws ReaderException, JSONException
+    {
+        String uri = groupObject.getString("uri");
+
+        // Group groupID
+        int index = uri.indexOf(AC.GROUP_URI);
+        if (index == -1)
+        {
+            String error = "group uri attribute malformed: " + uri;
+            throw new ReaderException(error);
+        }
+        String groupID = uri.substring(AC.GROUP_URI.length());
+
+        // Group owner
+        User<? extends Principal> user = null;
+        if (groupObject.has("owner"))
+        {
+            JSONObject ownerObject = groupObject.getJSONObject("owner");
+            JSONObject userObject = ownerObject.getJSONObject("user");
+            user = UserReader.parseUser(userObject);
+        }
+
+        Group group = new Group(groupID, user);
+
+        // description
+        if (groupObject.has("description"))
+        {
+            group.description = groupObject.getString("description");
+        }
+
+        // lastModified
+        if (groupObject.has("lastModified"))
+        {
+            try
+            {
+                DateFormat df = DateUtil.getDateFormat(DateUtil.IVOA_DATE_FORMAT, DateUtil.UTC);
+                group.lastModified = df.parse(groupObject.getString("lastModified"));
+            }
+            catch (ParseException e)
+            {
+                String error = "Unable to parse group lastModified because " + e.getMessage();
+
+                throw new ReaderException(error);
+            }
+        }
+
+        // properties
+        if (groupObject.has("description"))
+        {
+            JSONArray propertiesArray = groupObject.getJSONArray("properties");
+            for (int i = 0; i < propertiesArray.length(); i++)
+            {
+                JSONObject propertiesObject = propertiesArray.getJSONObject(i);
+                JSONObject propertyObject = propertiesObject.getJSONObject("property");
+                group.getProperties().add(GroupPropertyReader.read(propertyObject));
+            }
+        }
+
+        // groupMembers
+        if (groupObject.has("groupMembers"))
+        {
+            JSONArray groupMembersArray = groupObject.getJSONArray("groupMembers");
+            for (int i = 0; i < groupMembersArray.length(); i++)
+            {
+                JSONObject groupMembersObject = groupMembersArray.getJSONObject(i);
+                JSONObject groupMemberObject = groupMembersObject.getJSONObject("group");
+                group.getGroupMembers().add(parseGroup(groupMemberObject));
+            }
+        }
+
+        // userMembers
+        if (groupObject.has("userMembers"))
+        {
+            JSONArray userMembersArray = groupObject.getJSONArray("userMembers");
+            for (int i = 0; i < userMembersArray.length(); i++)
+            {
+                JSONObject userMemberObject = userMembersArray.getJSONObject(i);
+                JSONObject userObject = userMemberObject.getJSONObject("user");
+                group.getUserMembers().add(UserReader.parseUser(userObject));
+            }
+        }
+
+        // groupAdmins
+        if (groupObject.has("groupAdmins"))
+        {
+            JSONArray groupAdminsArray = groupObject.getJSONArray("groupAdmins");
+            for (int i = 0; i < groupAdminsArray.length(); i++)
+            {
+                JSONObject groupAdminsObject = groupAdminsArray.getJSONObject(i);
+                JSONObject groupAdminObject = groupAdminsObject.getJSONObject("group");
+                group.getGroupAdmins().add(parseGroup(groupAdminObject));
+            }
+        }
+
+        // userAdmins
+        if (groupObject.has("userAdmins"))
+        {
+            JSONArray userAdminsArray = groupObject.getJSONArray("userAdmins");
+            for (int i = 0; i < userAdminsArray.length(); i++)
+            {
+                JSONObject userAdminObject = userAdminsArray.getJSONObject(i);
+                JSONObject userObject = userAdminObject.getJSONObject("user");
+                group.getUserAdmins().add(UserReader.parseUser(userObject));
+            }
+        }
+
+        return group;
+    }
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupWriter.java
new file mode 100755
index 00000000..d5a1f1b6
--- /dev/null
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/GroupWriter.java
@@ -0,0 +1,257 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  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: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.json;
+
+import ca.nrc.cadc.ac.AC;
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupProperty;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.ac.WriterException;
+import ca.nrc.cadc.date.DateUtil;
+import ca.nrc.cadc.util.StringBuilderWriter;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.security.Principal;
+import java.text.DateFormat;
+
+public class GroupWriter
+{
+    /**
+     * Write a Group to a StringBuilder.
+     * @param group
+     * @param builder
+     * @throws IOException
+     * @throws WriterException
+     */
+    public static void write(Group group, StringBuilder builder)
+        throws IOException, WriterException
+    {
+        write(group, new StringBuilderWriter(builder));
+    }
+
+    /**
+     * Write a Group to an OutputStream.
+     *
+     * @param group Group to write.
+     * @param out OutputStream to write to.
+     * @throws IOException if the writer fails to write.
+     * @throws WriterException
+     */
+    public static void write(Group group, OutputStream out)
+        throws IOException, WriterException
+    {
+        OutputStreamWriter outWriter;
+        try
+        {
+            outWriter = new OutputStreamWriter(out, "UTF-8");
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException("UTF-8 encoding not supported", e);
+        }
+        write(group, new BufferedWriter(outWriter));
+    }
+
+    /**
+     * Write a Group to a Writer.
+     *
+     * @param group Group to write.
+     * @param writer  Writer to write to.
+     * @throws IOException if the writer fails to write.
+     * @throws WriterException
+     */
+    public static void write(Group group, Writer writer)
+        throws IOException, WriterException
+    {
+        if (group == null)
+        {
+            throw new WriterException("null group");
+        }
+
+        try
+        {
+            getGroupObject(group).write(writer);
+        }
+        catch (JSONException e)
+        {
+            final String error = "Unable to create JSON for Group " +
+                                 " because " + e.getMessage();
+            throw new WriterException(error, e);
+        }
+    }
+
+    /**
+     *
+     * @param group
+     * @return
+     * @throws WriterException
+     */
+    public static JSONObject getGroupObject(Group group)
+        throws WriterException, JSONException
+    {
+        return getGroupObject(group, true);
+    }
+
+    public static JSONObject getGroupObject(Group group, boolean deepCopy)
+        throws WriterException, JSONException
+    {
+        JSONObject groupObject = new JSONObject();
+        groupObject.put("uri", AC.GROUP_URI + group.getID());
+
+        // Group owner
+        if (group.getOwner() != null)
+        {
+            groupObject.put("owner", UserWriter.getUserObject(group.getOwner()));
+        }
+
+        if (deepCopy)
+        {
+            // Group description
+            if (group.description != null)
+            {
+                groupObject.put("description", group.description);
+            }
+
+            // lastModified
+            if (group.lastModified != null)
+            {
+                DateFormat df = DateUtil.getDateFormat(DateUtil.IVOA_DATE_FORMAT, DateUtil.UTC);
+                groupObject.put("lastModified", df.format(group.lastModified));
+            }
+
+            // Group properties
+            if (!group.getProperties().isEmpty())
+            {
+                JSONArray propertiesArray = new JSONArray();
+                for (GroupProperty property : group.getProperties())
+                {
+                    JSONObject propertyObject = new JSONObject();
+                    propertyObject.put("property", GroupPropertyWriter.write(property));
+                    propertiesArray.put(propertyObject);
+                }
+                groupObject.put("properties", propertiesArray);
+            }
+
+            // Group groupMembers.
+            if ((group.getGroupMembers() != null) && (!group.getGroupMembers().isEmpty()))
+            {
+                JSONArray groupMembersArray = new JSONArray();
+                for (Group groupMember : group.getGroupMembers())
+                {
+                    groupMembersArray.put(getGroupObject(groupMember, false));
+                }
+                groupObject.put("groupMembers", groupMembersArray);
+            }
+
+            // Group userMembers
+            if ((group.getUserMembers() != null) && (!group.getUserMembers().isEmpty()))
+            {
+                JSONArray userMembersArray = new JSONArray();
+                for (User<? extends Principal> userMember : group.getUserMembers())
+                {
+                    userMembersArray.put(UserWriter.getUserObject(userMember));
+                }
+                groupObject.put("userMembers", userMembersArray);
+            }
+
+            // Group groupAdmins.
+            if ((group.getGroupAdmins() != null) && (!group.getGroupAdmins().isEmpty()))
+            {
+                JSONArray groupAdminsArray = new JSONArray();
+                for (Group groupAdmin : group.getGroupAdmins())
+                {
+                    groupAdminsArray.put(getGroupObject(groupAdmin, false));
+                }
+                groupObject.put("groupAdmins", groupAdminsArray);
+            }
+
+            // Group userAdmins
+            if ((group.getUserAdmins() != null) && (!group.getUserAdmins().isEmpty()))
+            {
+                JSONArray userAdminsArray = new JSONArray();
+                for (User<? extends Principal> userAdmin : group.getUserAdmins())
+                {
+                    userAdminsArray.put(UserWriter.getUserObject(userAdmin));
+                }
+                groupObject.put("userAdmins", userAdminsArray);
+            }
+        }
+
+        return new JSONObject().put("group", groupObject);
+    }
+
+}
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserReader.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserReader.java
index 1f9fa208..d91932a3 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserReader.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserReader.java
@@ -78,7 +78,6 @@ import org.json.JSONObject;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
-import java.net.URISyntaxException;
 import java.security.Principal;
 import java.util.Scanner;
 
@@ -91,10 +90,9 @@ public class UserReader
      * @return User User.
      * @throws ReaderException
      * @throws IOException
-     * @throws URISyntaxException
      */
     public static User<? extends Principal> read(InputStream in)
-        throws ReaderException, IOException, URISyntaxException
+        throws ReaderException, IOException
     {
         if (in == null)
         {
@@ -116,7 +114,7 @@ public class UserReader
      * @throws IOException
      */
     public static User<? extends Principal> read(Reader reader)
-        throws ReaderException, IOException, URISyntaxException
+        throws ReaderException, IOException
     {
         if (reader == null)
         {
@@ -136,10 +134,9 @@ public class UserReader
      * @return User User.
      * @throws ReaderException
      * @throws IOException
-     * @throws URISyntaxException
      */
     public static User<? extends Principal> read(String json)
-        throws ReaderException, IOException, URISyntaxException
+        throws ReaderException, IOException
     {
         if (json == null || json.isEmpty())
         {
@@ -160,17 +157,17 @@ public class UserReader
     }
 
     protected static User<? extends Principal> parseUser(JSONObject userObject)
-        throws ReaderException
+        throws ReaderException, JSONException
     {
-        try
-        {
-            JSONObject userIDObject = userObject.getJSONObject("userID");
-            JSONObject userIDIdentityObject = userIDObject.getJSONObject("identity");
+        JSONObject userIDObject = userObject.getJSONObject("userID");
+        JSONObject userIDIdentityObject = userIDObject.getJSONObject("identity");
 
-            Principal userID = IdentityReader.read(userIDIdentityObject);
-            User<Principal> user = new User<Principal>(userID);
+        Principal userID = IdentityReader.read(userIDIdentityObject);
+        User<Principal> user = new User<Principal>(userID);
 
-            // identities
+        // identities
+        if (userObject.has("identities"))
+        {
             JSONArray identitiesArray = userObject.getJSONArray("identities");
             for (int i = 0; i < identitiesArray.length(); i++)
             {
@@ -178,8 +175,11 @@ public class UserReader
                 JSONObject identityObject = identitiesObject.getJSONObject(("identity"));
                 user.getIdentities().add(IdentityReader.read(identityObject));
             }
+        }
 
-            // details
+        // details
+        if (userObject.has("details"))
+        {
             JSONArray detailsArray = userObject.getJSONArray("details");
             for (int i = 0; i < detailsArray.length(); i++)
             {
@@ -187,15 +187,9 @@ public class UserReader
                 JSONObject userDetailsObject = detailsObject.getJSONObject(UserDetails.NAME);
                 user.details.add(UserDetailsReader.read(userDetailsObject));
             }
-
-            return user;
-        }
-        catch (JSONException e)
-        {
-            final String error = "Unable to parse JSON to User because " +
-                                 e.getMessage();
-            throw new ReaderException(error, e);
         }
+
+        return user;
     }
 
 }
diff --git a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserWriter.java b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserWriter.java
index 1289ac9e..28652358 100755
--- a/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserWriter.java
+++ b/projects/cadcAccessControl/src/ca/nrc/cadc/ac/json/UserWriter.java
@@ -142,7 +142,7 @@ public class UserWriter
 
         try
         {
-            getUserJSONObject(user).write(writer);
+            getUserObject(user).write(writer);
         }
         catch (JSONException e)
         {
@@ -159,7 +159,7 @@ public class UserWriter
      * @return JSONObject.
      * @throws WriterException
      */
-    public static JSONObject getUserJSONObject(User<? extends Principal> user)
+    public static JSONObject getUserObject(User<? extends Principal> user)
         throws WriterException, JSONException
     {
         JSONObject userObject = new JSONObject();
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/GroupReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/GroupReaderWriterTest.java
new file mode 100644
index 00000000..b60eee86
--- /dev/null
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/GroupReaderWriterTest.java
@@ -0,0 +1,189 @@
+/*
+ ************************************************************************
+ *******************  CANADIAN ASTRONOMY DATA CENTRE  *******************
+ **************  CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES  **************
+ *
+ *  (c) 2014.                            (c) 2014.
+ *  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: 4 $
+ *
+ ************************************************************************
+ */
+package ca.nrc.cadc.ac.json;
+
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.GroupProperty;
+import ca.nrc.cadc.ac.User;
+import ca.nrc.cadc.ac.WriterException;
+import ca.nrc.cadc.auth.HttpPrincipal;
+import ca.nrc.cadc.auth.OpenIdPrincipal;
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
+import javax.security.auth.x500.X500Principal;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.security.Principal;
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+/**
+ *
+ * @author jburke
+ */
+public class GroupReaderWriterTest
+{
+    private static Logger log = Logger.getLogger(GroupReaderWriterTest.class);
+
+    @Test
+    public void testReaderExceptions()
+        throws Exception
+    {
+        try
+        {
+            String s = null;
+            Group g = GroupReader.read(s);
+            fail("null String should throw IllegalArgumentException");
+        }
+        catch (IllegalArgumentException e) {}
+        
+        try
+        {
+            InputStream in = null;
+            Group g = GroupReader.read(in);
+            fail("null InputStream should throw IOException");
+        }
+        catch (IOException e) {}
+        
+        try
+        {
+            Reader r = null;
+            Group g = GroupReader.read(r);
+            fail("null element should throw ReaderException");
+        }
+        catch (IllegalArgumentException e) {}
+    }
+     
+    @Test
+    public void testWriterExceptions()
+        throws Exception
+    {
+        try
+        {
+            GroupWriter.write(null, new StringBuilder());
+            fail("null Group should throw WriterException");
+        }
+        catch (WriterException e) {}
+    }
+     
+    @Test
+    public void testMinimalReadWrite()
+        throws Exception
+    {
+        Group expected = new Group("groupID", null);
+                
+        StringBuilder json = new StringBuilder();
+        GroupWriter.write(expected, json);
+        assertFalse(json.toString().isEmpty());
+        
+        Group actual = GroupReader.read(json.toString());
+        assertNotNull(actual);
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void testMaximalReadWrite()
+        throws Exception
+    {
+        Group expected = new Group("groupID", new User<Principal>(new HttpPrincipal("foo")));
+        expected.description = "description";
+        expected.lastModified = new Date();
+        expected.getProperties().add(new GroupProperty("key", "value", true));
+        
+        Group groupMember = new Group("member", new User<Principal>(new OpenIdPrincipal("bar")));
+        User<Principal> userMember = new User<Principal>(new HttpPrincipal("baz"));
+        Group groupAdmin = new Group("admin", new User<Principal>(new X500Principal("cn=foo,o=ca")));
+        User<Principal> userAdmin = new User<Principal>(new HttpPrincipal("admin"));
+        
+        expected.getGroupMembers().add(groupMember);
+        expected.getUserMembers().add(userMember);
+        expected.getGroupAdmins().add(groupAdmin);
+        expected.getUserAdmins().add(userAdmin);
+        
+        StringBuilder json = new StringBuilder();
+        GroupWriter.write(expected, json);
+        assertFalse(json.toString().isEmpty());
+
+        Group actual = GroupReader.read(json.toString());
+        assertNotNull(actual);
+        assertEquals(expected, actual);
+        assertEquals(expected.description, actual.description);
+        assertEquals(expected.lastModified, actual.lastModified);
+        assertEquals(expected.getProperties(), actual.getProperties());
+        assertEquals(expected.getGroupMembers(), actual.getGroupMembers());
+        assertEquals(expected.getUserMembers(), actual.getUserMembers());
+    }
+    
+}
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/UserReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/UserReaderWriterTest.java
index 68901a36..6e468da8 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/UserReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/json/UserReaderWriterTest.java
@@ -69,6 +69,7 @@
 package ca.nrc.cadc.ac.json;
 
 import ca.nrc.cadc.ac.PersonalDetails;
+import ca.nrc.cadc.ac.PosixDetails;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.WriterException;
 import ca.nrc.cadc.auth.HttpPrincipal;
@@ -142,7 +143,8 @@ public class UserReaderWriterTest
         User<? extends Principal> expected = new User<Principal>(new HttpPrincipal("foo"));
         expected.getIdentities().add(new NumericPrincipal(123l));
         expected.details.add(new PersonalDetails("firstname", "lastname"));
-        
+        expected.details.add(new PosixDetails(123l, 456l, "foo"));
+
         StringBuilder json = new StringBuilder();
         UserWriter.write(expected, json);
         assertFalse(json.toString().isEmpty());
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java
index 5902daa4..7075c9e1 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupPropertyReaderWriterTest.java
@@ -100,7 +100,7 @@ public class GroupPropertyReaderWriterTest
         Element element = null;
         try
         {
-            GroupProperty gp = ca.nrc.cadc.ac.xml.GroupPropertyReader.read(element);
+            GroupProperty gp = GroupPropertyReader.read(element);
             fail("null element should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -108,7 +108,7 @@ public class GroupPropertyReaderWriterTest
         element = new Element("foo");
         try
         {
-            GroupProperty gp = ca.nrc.cadc.ac.xml.GroupPropertyReader.read(element);
+            GroupProperty gp = GroupPropertyReader.read(element);
             fail("element not named 'property' should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -116,7 +116,7 @@ public class GroupPropertyReaderWriterTest
         element = new Element("property");
         try
         {
-            GroupProperty gp = ca.nrc.cadc.ac.xml.GroupPropertyReader.read(element);
+            GroupProperty gp = GroupPropertyReader.read(element);
             fail("element without 'key' attribute should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -124,7 +124,7 @@ public class GroupPropertyReaderWriterTest
         element.setAttribute("key", "foo");
         try
         {
-            GroupProperty gp = ca.nrc.cadc.ac.xml.GroupPropertyReader.read(element);
+            GroupProperty gp = GroupPropertyReader.read(element);
             fail("element without 'type' attribute should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -132,7 +132,7 @@ public class GroupPropertyReaderWriterTest
         element.setAttribute("type", "Double");
         try
         {
-            GroupProperty gp = ca.nrc.cadc.ac.xml.GroupPropertyReader.read(element);
+            GroupProperty gp = GroupPropertyReader.read(element);
             fail("Unsupported 'type' should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -144,7 +144,7 @@ public class GroupPropertyReaderWriterTest
     {
         try
         {
-            Element element = ca.nrc.cadc.ac.xml.GroupPropertyWriter.write(null);
+            Element element = GroupPropertyWriter.write(null);
             fail("null GroupProperty should throw WriterException");
         }
         catch (WriterException e) {}
@@ -152,7 +152,7 @@ public class GroupPropertyReaderWriterTest
         GroupProperty gp = new GroupProperty("key", new Double(1.0), true);
         try
         {
-            Element element = ca.nrc.cadc.ac.xml.GroupPropertyWriter.write(gp);
+            Element element = GroupPropertyWriter.write(gp);
             fail("Unsupported GroupProperty type should throw IllegalArgumentException");
         }
         catch (IllegalArgumentException e) {}
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupReaderWriterTest.java
index ba058861..12f3f509 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupReaderWriterTest.java
@@ -68,29 +68,26 @@
  */
 package ca.nrc.cadc.ac.xml;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.security.Principal;
-import java.util.Date;
-
-import javax.security.auth.x500.X500Principal;
-
 import ca.nrc.cadc.ac.Group;
 import ca.nrc.cadc.ac.GroupProperty;
 import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.WriterException;
+import ca.nrc.cadc.auth.HttpPrincipal;
+import ca.nrc.cadc.auth.OpenIdPrincipal;
 import org.apache.log4j.Logger;
 import org.junit.Test;
 
-import ca.nrc.cadc.auth.HttpPrincipal;
-import ca.nrc.cadc.auth.OpenIdPrincipal;
-import static org.junit.Assert.assertTrue;
+import javax.security.auth.x500.X500Principal;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.security.Principal;
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 /**
  *
@@ -107,7 +104,7 @@ public class GroupReaderWriterTest
         try
         {
             String s = null;
-            Group g = ca.nrc.cadc.ac.xml.GroupReader.read(s);
+            Group g = GroupReader.read(s);
             fail("null String should throw IllegalArgumentException");
         }
         catch (IllegalArgumentException e) {}
@@ -115,7 +112,7 @@ public class GroupReaderWriterTest
         try
         {
             InputStream in = null;
-            Group g = ca.nrc.cadc.ac.xml.GroupReader.read(in);
+            Group g = GroupReader.read(in);
             fail("null InputStream should throw IOException");
         }
         catch (IOException e) {}
@@ -123,7 +120,7 @@ public class GroupReaderWriterTest
         try
         {
             Reader r = null;
-            Group g = ca.nrc.cadc.ac.xml.GroupReader.read(r);
+            Group g = GroupReader.read(r);
             fail("null element should throw ReaderException");
         }
         catch (IllegalArgumentException e) {}
@@ -135,7 +132,7 @@ public class GroupReaderWriterTest
     {
         try
         {
-            ca.nrc.cadc.ac.xml.GroupWriter.write(null, new StringBuilder());
+            GroupWriter.write(null, new StringBuilder());
             fail("null Group should throw WriterException");
         }
         catch (WriterException e) {}
@@ -148,10 +145,10 @@ public class GroupReaderWriterTest
         Group expected = new Group("groupID", null);
                 
         StringBuilder xml = new StringBuilder();
-        ca.nrc.cadc.ac.xml.GroupWriter.write(expected, xml);
+        GroupWriter.write(expected, xml);
         assertFalse(xml.toString().isEmpty());
         
-        Group actual = ca.nrc.cadc.ac.xml.GroupReader.read(xml.toString());
+        Group actual = GroupReader.read(xml.toString());
         assertNotNull(actual);
         assertEquals(expected, actual);
     }
@@ -176,10 +173,10 @@ public class GroupReaderWriterTest
         expected.getUserAdmins().add(userAdmin);
         
         StringBuilder xml = new StringBuilder();
-        ca.nrc.cadc.ac.xml.GroupWriter.write(expected, xml);
+        GroupWriter.write(expected, xml);
         assertFalse(xml.toString().isEmpty());
         
-        Group actual = ca.nrc.cadc.ac.xml.GroupReader.read(xml.toString());
+        Group actual = GroupReader.read(xml.toString());
         assertNotNull(actual);
         assertEquals(expected, actual);
         assertEquals(expected.description, actual.description);
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupsReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupsReaderWriterTest.java
index 3eee8859..1d4d9f30 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupsReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/GroupsReaderWriterTest.java
@@ -68,20 +68,21 @@
  */
 package ca.nrc.cadc.ac.xml;
 
+import ca.nrc.cadc.ac.Group;
+import ca.nrc.cadc.ac.WriterException;
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
 import java.util.ArrayList;
 import java.util.List;
 
-import ca.nrc.cadc.ac.Group;
-import ca.nrc.cadc.ac.WriterException;
-import org.apache.log4j.Logger;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
-import org.junit.Test;
 
 /**
  *
@@ -98,7 +99,7 @@ public class GroupsReaderWriterTest
         try
         {
             String s = null;
-            List<Group> g = ca.nrc.cadc.ac.xml.GroupsReader.read(s);
+            List<Group> g = GroupsReader.read(s);
             fail("null String should throw IllegalArgumentException");
         }
         catch (IllegalArgumentException e) {}
@@ -106,7 +107,7 @@ public class GroupsReaderWriterTest
         try
         {
             InputStream in = null;
-            List<Group> g = ca.nrc.cadc.ac.xml.GroupsReader.read(in);
+            List<Group> g = GroupsReader.read(in);
             fail("null InputStream should throw IOException");
         }
         catch (IOException e) {}
@@ -114,7 +115,7 @@ public class GroupsReaderWriterTest
         try
         {
             Reader r = null;
-            List<Group> g = ca.nrc.cadc.ac.xml.GroupsReader.read(r);
+            List<Group> g = GroupsReader.read(r);
             fail("null element should throw ReaderException");
         }
         catch (IllegalArgumentException e) {}
@@ -126,7 +127,7 @@ public class GroupsReaderWriterTest
     {
         try
         {
-            ca.nrc.cadc.ac.xml.GroupsWriter.write(null, new StringBuilder());
+            GroupsWriter.write(null, new StringBuilder());
             fail("null Group should throw WriterException");
         }
         catch (WriterException e) {}
@@ -141,10 +142,10 @@ public class GroupsReaderWriterTest
         expected.add(new Group("group2", null));
         
         StringBuilder xml = new StringBuilder();
-        ca.nrc.cadc.ac.xml.GroupsWriter.write(expected, xml);
+        GroupsWriter.write(expected, xml);
         assertFalse(xml.toString().isEmpty());
         
-        List<Group> actual = ca.nrc.cadc.ac.xml.GroupsReader.read(xml.toString());
+        List<Group> actual = GroupsReader.read(xml.toString());
         assertNotNull(actual);
         assertEquals(expected.size(), actual.size());
         assertEquals(expected.get(0), actual.get(0));
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java
index 07894640..58918c8d 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/IdentityReaderWriterTest.java
@@ -73,13 +73,17 @@ import ca.nrc.cadc.ac.WriterException;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import ca.nrc.cadc.auth.NumericPrincipal;
 import ca.nrc.cadc.auth.OpenIdPrincipal;
-import java.security.Principal;
-import javax.management.remote.JMXPrincipal;
-import javax.security.auth.x500.X500Principal;
 import org.apache.log4j.Logger;
 import org.jdom2.Element;
 import org.junit.Test;
-import static org.junit.Assert.*;
+
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.x500.X500Principal;
+import java.security.Principal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 /**
  *
@@ -96,7 +100,7 @@ public class IdentityReaderWriterTest
         Element element = null;
         try
         {
-            Principal p = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+            Principal p = IdentityReader.read(element);
             fail("null element should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -104,7 +108,7 @@ public class IdentityReaderWriterTest
         element = new Element("foo");
         try
         {
-            Principal p = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+            Principal p = IdentityReader.read(element);
             fail("element not named 'identity' should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -112,7 +116,7 @@ public class IdentityReaderWriterTest
         element = new Element("identity");
         try
         {
-            Principal p = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+            Principal p = IdentityReader.read(element);
             fail("element without 'type' attribute should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -120,7 +124,7 @@ public class IdentityReaderWriterTest
         element.setAttribute("type", "foo");
         try
         {
-            Principal p = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+            Principal p = IdentityReader.read(element);
             fail("element with unknown 'type' attribute should throw ReaderException");
         }
         catch (ReaderException e) {}
@@ -132,7 +136,7 @@ public class IdentityReaderWriterTest
     {
         try
         {
-            Element element = ca.nrc.cadc.ac.xml.IdentityWriter.write(null);
+            Element element = IdentityWriter.write(null);
             fail("null Identity should throw WriterException");
         }
         catch (WriterException e) {}
@@ -140,7 +144,7 @@ public class IdentityReaderWriterTest
         Principal p = new JMXPrincipal("foo");
         try
         {
-            Element element = ca.nrc.cadc.ac.xml.IdentityWriter.write(p);
+            Element element = IdentityWriter.write(p);
             fail("Unsupported Principal type should throw IllegalArgumentException");
         }
         catch (IllegalArgumentException e) {}
@@ -152,40 +156,40 @@ public class IdentityReaderWriterTest
     {
         // X500
         Principal expected = new X500Principal("cn=foo,o=bar");
-        Element element = ca.nrc.cadc.ac.xml.IdentityWriter.write(expected);
+        Element element = IdentityWriter.write(expected);
         assertNotNull(element);
          
-        Principal actual = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+        Principal actual = IdentityReader.read(element);
         assertNotNull(actual);
          
         assertEquals(expected, actual);
          
         // UID
         expected = new NumericPrincipal(123l);
-        element = ca.nrc.cadc.ac.xml.IdentityWriter.write(expected);
+        element = IdentityWriter.write(expected);
         assertNotNull(element);
          
-        actual = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+        actual = IdentityReader.read(element);
         assertNotNull(actual);
          
         assertEquals(expected, actual);
         
         // OpenID
         expected = new OpenIdPrincipal("bar");
-        element = ca.nrc.cadc.ac.xml.IdentityWriter.write(expected);
+        element = IdentityWriter.write(expected);
         assertNotNull(element);
          
-        actual = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+        actual = IdentityReader.read(element);
         assertNotNull(actual);
          
         assertEquals(expected, actual);
         
         // HTTP
         expected = new HttpPrincipal("baz");
-        element = ca.nrc.cadc.ac.xml.IdentityWriter.write(expected);
+        element = IdentityWriter.write(expected);
         assertNotNull(element);
          
-        actual = ca.nrc.cadc.ac.xml.IdentityReader.read(element);
+        actual = IdentityReader.read(element);
         assertNotNull(actual);
          
         assertEquals(expected, actual);
diff --git a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserReaderWriterTest.java b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserReaderWriterTest.java
index ee4ad29a..a0e3d548 100644
--- a/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserReaderWriterTest.java
+++ b/projects/cadcAccessControl/test/src/ca/nrc/cadc/ac/xml/UserReaderWriterTest.java
@@ -73,13 +73,18 @@ import ca.nrc.cadc.ac.User;
 import ca.nrc.cadc.ac.WriterException;
 import ca.nrc.cadc.auth.HttpPrincipal;
 import ca.nrc.cadc.auth.NumericPrincipal;
+import org.apache.log4j.Logger;
+import org.junit.Test;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
 import java.security.Principal;
-import org.apache.log4j.Logger;
-import org.junit.Test;
-import static org.junit.Assert.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 /**
  *
@@ -96,7 +101,7 @@ public class UserReaderWriterTest
         try
         {
             String s = null;
-            User<? extends Principal> u = ca.nrc.cadc.ac.xml.UserReader.read(s);
+            User<? extends Principal> u = UserReader.read(s);
             fail("null String should throw IllegalArgumentException");
         }
         catch (IllegalArgumentException e) {}
@@ -104,7 +109,7 @@ public class UserReaderWriterTest
         try
         {
             InputStream in = null;
-            User<? extends Principal> u = ca.nrc.cadc.ac.xml.UserReader.read(in);
+            User<? extends Principal> u = UserReader.read(in);
             fail("null InputStream should throw IOException");
         }
         catch (IOException e) {}
@@ -112,7 +117,7 @@ public class UserReaderWriterTest
         try
         {
             Reader r = null;
-            User<? extends Principal> u = ca.nrc.cadc.ac.xml.UserReader.read(r);
+            User<? extends Principal> u = UserReader.read(r);
             fail("null Reader should throw IllegalArgumentException");
         }
         catch (IllegalArgumentException e) {}
@@ -139,10 +144,10 @@ public class UserReaderWriterTest
         expected.details.add(new PersonalDetails("firstname", "lastname"));
         
         StringBuilder xml = new StringBuilder();
-        ca.nrc.cadc.ac.xml.UserWriter.write(expected, xml);
+        UserWriter.write(expected, xml);
         assertFalse(xml.toString().isEmpty());
         
-        User<? extends Principal> actual = ca.nrc.cadc.ac.xml.UserReader.read(xml.toString());
+        User<? extends Principal> actual = UserReader.read(xml.toString());
         assertNotNull(actual);
         assertEquals(expected, actual);
     }
-- 
GitLab