ability to create xml groups definitions
diff --git a/src/com/google/enterprise/adaptor/GsaFeedFileMaker.java b/src/com/google/enterprise/adaptor/GsaFeedFileMaker.java
index 022b638..9b256cc 100644
--- a/src/com/google/enterprise/adaptor/GsaFeedFileMaker.java
+++ b/src/com/google/enterprise/adaptor/GsaFeedFileMaker.java
@@ -163,21 +163,25 @@
     }
     boolean noCase = acl.isEverythingCaseInsensitive();
     for (UserPrincipal permitUser : acl.getPermitUsers()) {
-      constructPrincipal(doc, aclElement, "permit", permitUser, noCase);
+      constructMetadataAndUrlPrincipal(doc, aclElement, "permit",
+          permitUser, noCase);
     }
     for (GroupPrincipal permitGroup : acl.getPermitGroups()) {
-      constructPrincipal(doc, aclElement, "permit", permitGroup, noCase);
+      constructMetadataAndUrlPrincipal(doc, aclElement, "permit",
+          permitGroup, noCase);
     }
     for (UserPrincipal denyUser : acl.getDenyUsers()) {
-      constructPrincipal(doc, aclElement, "deny", denyUser, noCase);
+      constructMetadataAndUrlPrincipal(doc, aclElement, "deny",
+          denyUser, noCase);
     }
     for (GroupPrincipal denyGroup : acl.getDenyGroups()) {
-      constructPrincipal(doc, aclElement, "deny", denyGroup, noCase);
+      constructMetadataAndUrlPrincipal(doc, aclElement, "deny",
+          denyGroup, noCase);
     }
   }
 
-  private void constructPrincipal(Document doc, Element acl, String access,
-      Principal principal, boolean everythingCaseInsensitive) {
+  private void constructMetadataAndUrlPrincipal(Document doc, Element acl,
+      String access, Principal principal, boolean everythingCaseInsensitive) {
     String scope = principal.isUser() ? "user" : "group";
     Element principalElement = doc.createElement("principal");
     principalElement.setAttribute("scope", scope);
@@ -244,7 +248,7 @@
      provided DocIds and source name.  Is used by
      GsaCommunicationHandler.pushDocIds(). */
   public String makeMetadataAndUrlXml(String srcName,
-                                      List<? extends DocIdSender.Item> items) {
+      List<? extends DocIdSender.Item> items) {
     try {
       DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
       DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
@@ -260,4 +264,74 @@
       throw new IllegalStateException(pce);
     }
   }
+
+  /** Creates single group definition of group principal key and members. */
+  private void constructSingleMembership(Document doc, Element root,
+      GroupPrincipal groupPrincipal, List<Principal> members,
+      boolean caseSensitiveMembers) {
+    Element groupWithDef = doc.createElement("membership");
+    root.appendChild(groupWithDef);
+    Element groupKey = doc.createElement("principal");
+    groupWithDef.appendChild(groupKey);
+    groupKey.setAttribute("namespace", groupPrincipal.getNamespace());
+    groupKey.appendChild(doc.createTextNode(groupPrincipal.getName()));
+    Element groupDef = doc.createElement("members");
+    groupWithDef.appendChild(groupDef);
+    for (Principal member : members) {
+      Element groupDefElement = doc.createElement("principal");
+      groupDefElement.setAttribute("namespace", member.getNamespace());
+      String scope = member.isUser() ? "USER" : "GROUP";
+      groupDefElement.setAttribute("scope", scope);
+      if (caseSensitiveMembers) {
+        groupDefElement.setAttribute(
+            "case-sensitivity-type", "EVERYTHING_CASE_SENSITIVE");
+      } else {
+        groupDefElement.setAttribute(
+            "case-sensitivity-type", "EVERYTHING_CASE_INSENSITIVE");
+      }
+      groupDefElement.appendChild(doc.createTextNode(member.getName()));
+      groupDef.appendChild(groupDefElement);
+    }
+  }
+
+  /** Adds all the groups' definitions into body. */
+  private void constructGroupsDefinitionsFileBody(Document doc,
+      Element root, Map<GroupPrincipal, List<Principal>> items,
+      boolean caseSensitiveMembers) {
+    for (Map.Entry<GroupPrincipal, List<Principal>> group : items.entrySet()) {
+      constructSingleMembership(doc, root, group.getKey(), group.getValue(),
+          caseSensitiveMembers);
+    }
+  }
+
+  /** Puts all groups' definitions into document. */
+  private void constructGroupsDefinitionsFeedFile(Document doc,
+      Map<GroupPrincipal, List<Principal>> items,
+      boolean caseSensitiveMembers) {
+    Element root = doc.createElement("xmlgroups");
+    doc.appendChild(root);
+    Comment comment = doc.createComment("GSA EasyConnector");
+    root.appendChild(comment);
+    constructGroupsDefinitionsFileBody(doc, root, items, caseSensitiveMembers);
+  }
+
+  /** Makes feed file with groups and their definitions. */
+  public String makeGroupsDefinitionsXml(
+      Map<GroupPrincipal, List<Principal>> items,
+      boolean caseSensitiveMembers) {
+    try {
+      DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
+      DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
+      Document doc = docBuilder.newDocument();
+      constructGroupsDefinitionsFeedFile(doc, items, caseSensitiveMembers);
+      String xmlString = documentToString(doc); 
+      return xmlString;
+    } catch (TransformerConfigurationException tce) {
+      throw new IllegalStateException(tce);
+    } catch (TransformerException te) {
+      throw new IllegalStateException(te);
+    } catch (ParserConfigurationException pce) {
+      throw new IllegalStateException(pce);
+    }
+  }
 }
diff --git a/test/com/google/enterprise/adaptor/GsaFeedFileMakerTest.java b/test/com/google/enterprise/adaptor/GsaFeedFileMakerTest.java
index 6e965f0..66b7651 100644
--- a/test/com/google/enterprise/adaptor/GsaFeedFileMakerTest.java
+++ b/test/com/google/enterprise/adaptor/GsaFeedFileMakerTest.java
@@ -31,7 +31,7 @@
   private GsaFeedFileMaker meker = new GsaFeedFileMaker(encoder);
 
   @Test
-  public void testEmpty() {
+  public void testEmptyMetadataAndUrl() {
     String golden =
         "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
         + "<!DOCTYPE gsafeed PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
@@ -50,7 +50,7 @@
   }
 
   @Test
-  public void testSimple() {
+  public void testSimpleMetadataAndUrl() {
     String golden =
         "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
         + "<!DOCTYPE gsafeed PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
@@ -76,7 +76,8 @@
   }
 
   @Test
-  public void testPushAttributes() throws java.net.URISyntaxException {
+  public void testPushAttributesMetadataAndUrl()
+      throws java.net.URISyntaxException {
     String golden =
         "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
         + "<!DOCTYPE gsafeed PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
@@ -134,7 +135,7 @@
   }
 
   @Test
-  public void testNamedResources() {
+  public void testNamedResourcesMetadataAndUrl() {
     String golden
         = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
         + "<!DOCTYPE gsafeed PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
@@ -197,7 +198,7 @@
   }
 
   @Test
-  public void testUnsupportedDocIdSenderItem() {
+  public void testUnsupportedDocIdSenderItemMetadataAndUrl() {
     class UnsupportedItem implements DocIdSender.Item {};
     List<UnsupportedItem> items = new ArrayList<UnsupportedItem>();
     items.add(new UnsupportedItem());
@@ -258,7 +259,7 @@
   }
 
   @Test
-  public void testAclFragment() {
+  public void testAclFragmentMetadataAndUrl() {
     String golden
         = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
         + "<!DOCTYPE gsafeed PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
@@ -282,4 +283,133 @@
     xml = xml.replace("\r\n", "\n");
     assertEquals(golden, xml);
   }
+
+  @Test
+  public void testEmptyGroupsDefinitions() {
+    String golden =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+        + "<!DOCTYPE xmlgroups PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
+        + "<xmlgroups>\n"
+        + "<!--GSA EasyConnector-->\n"
+        + "</xmlgroups>\n";
+    String xml = meker.makeGroupsDefinitionsXml(
+        new TreeMap<GroupPrincipal, List<Principal>>(), true);
+    xml = xml.replaceAll("\r\n", "\n");
+    assertEquals(golden, xml);
+  }
+
+  @Test
+  public void testSimpleGroupsDefinitions() {
+    String golden =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+        + "<!DOCTYPE xmlgroups PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
+        + "<xmlgroups>\n"
+        + "<!--GSA EasyConnector-->\n"
+        + "<membership>\n"
+        + "<principal namespace=\"Default\">immortals</principal>\n"
+        + "<members>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_INSENSITIVE\""
+        + " namespace=\"Default\""
+        + " scope=\"USER\""
+        + ">MacLeod\\Duncan</principal>\n"
+        + "</members>\n"
+        + "</membership>\n"
+        + "</xmlgroups>\n";
+    Map<GroupPrincipal, List<Principal>> groupDefs
+        = new TreeMap<GroupPrincipal, List<Principal>>();
+    List<Principal> members = new ArrayList<Principal>();
+    members.add(new UserPrincipal("MacLeod\\Duncan"));
+    groupDefs.put(new GroupPrincipal("immortals"), members);
+    String xml = meker.makeGroupsDefinitionsXml(groupDefs, false);
+    xml = xml.replaceAll("\r\n", "\n");
+    assertEquals(golden, xml);
+  }
+
+  @Test
+  public void testMultipleGroupsDefinitions() {
+    String golden =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+        + "<!DOCTYPE xmlgroups PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
+        + "<xmlgroups>\n"
+        + "<!--GSA EasyConnector-->\n"
+        + "<membership>\n"
+        + "<principal namespace=\"Default\">immortals</principal>\n"
+        + "<members>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_INSENSITIVE\""
+        + " namespace=\"Default\""
+        + " scope=\"USER\""
+        + ">MacLeod\\Duncan</principal>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_INSENSITIVE\""
+        + " namespace=\"Default\""
+        + " scope=\"USER\""
+        + ">Methos</principal>\n"
+        + "</members>\n"
+        + "</membership>\n"
+        + "<membership>\n"
+        + "<principal namespace=\"Default\">sounds</principal>\n"
+        + "<members>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_INSENSITIVE\""
+        + " namespace=\"Default\""
+        + " scope=\"USER\""
+        + ">splat</principal>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_INSENSITIVE\""
+        + " namespace=\"Default\""
+        + " scope=\"USER\""
+        + ">plump</principal>\n"
+        + "</members>\n"
+        + "</membership>\n"
+        + "</xmlgroups>\n";
+    Map<GroupPrincipal, List<Principal>> groupDefs
+        = new TreeMap<GroupPrincipal, List<Principal>>();
+    List<Principal> members = new ArrayList<Principal>();
+    members.add(new UserPrincipal("MacLeod\\Duncan"));
+    members.add(new UserPrincipal("Methos"));
+    groupDefs.put(new GroupPrincipal("immortals"), members);
+    List<Principal> members2 = new ArrayList<Principal>();
+    members2.add(new UserPrincipal("splat"));
+    members2.add(new UserPrincipal("plump"));
+    groupDefs.put(new GroupPrincipal("sounds"), members2);
+    String xml = meker.makeGroupsDefinitionsXml(groupDefs, false);
+    xml = xml.replaceAll("\r\n", "\n");
+    assertEquals(golden, xml);
+  }
+
+  @Test
+  public void testNestedGroupsDefinitions() {
+    String golden =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
+        + "<!DOCTYPE xmlgroups PUBLIC \"-//Google//DTD GSA Feeds//EN\" \"\">\n"
+        + "<xmlgroups>\n"
+        + "<!--GSA EasyConnector-->\n"
+        + "<membership>\n"
+        + "<principal namespace=\"Default\">immortals</principal>\n"
+        + "<members>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_SENSITIVE\""
+        + " namespace=\"goodguys\""
+        + " scope=\"USER\""
+        + ">MacLeod\\Duncan</principal>\n"
+        + "<principal" 
+        + " case-sensitivity-type=\"EVERYTHING_CASE_SENSITIVE\""
+        + " namespace=\"3vil\""
+        + " scope=\"GROUP\""
+        + ">badguys</principal>\n"
+        + "</members>\n"
+        + "</membership>\n"
+        + "</xmlgroups>\n";
+    Map<GroupPrincipal, List<Principal>> groupDefs
+        = new TreeMap<GroupPrincipal, List<Principal>>();
+    List<Principal> members = new ArrayList<Principal>();
+    members.add(new UserPrincipal("MacLeod\\Duncan", "goodguys"));
+    members.add(new GroupPrincipal("badguys", "3vil"));
+    groupDefs.put(new GroupPrincipal("immortals"), members);
+    String xml = meker.makeGroupsDefinitionsXml(groupDefs, true);
+    xml = xml.replaceAll("\r\n", "\n");
+    assertEquals(golden, xml);
+  }
 }