namespaced acl support in command-line adaptor
diff --git a/src/com/google/enterprise/adaptor/CommandStreamParser.java b/src/com/google/enterprise/adaptor/CommandStreamParser.java
index 0da27bd..045d03d 100644
--- a/src/com/google/enterprise/adaptor/CommandStreamParser.java
+++ b/src/com/google/enterprise/adaptor/CommandStreamParser.java
@@ -26,6 +26,8 @@
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
@@ -240,7 +242,6 @@
  */
 public class CommandStreamParser {
 
-
   private static enum Operation {
     ID("id"),
     RESULT_LINK("result-link"),
@@ -263,6 +264,17 @@
     NO_FOLLOW("no-follow"),
     NO_ARCHIVE("no-archive"),
     DISPLAY_URL("display-url"),
+    ACL("acl"),
+    NAMESPACE("namespace"),
+    ACL_PERMIT_USER("acl-permit-user"),
+    ACL_DENY_USER("acl-deny-user"),
+    ACL_PERMIT_GROUP("acl-permit-group"),
+    ACL_DENY_GROUP("acl-deny-group"),
+    ACL_INHERIT_FROM("acl-inherit-from"),
+    ACL_INHERIT_FRAGMENT("acl-inherit-fragment"),
+    ACL_INHERITANCE_TYPE("acl-inheritance-type"),
+    ACL_CASE_SENSITIVE("acl-case-sensitive"),
+    ACL_CASE_INSENSITIVE("acl-case-insensitive"),
     ;
 
     private final String commandName;
@@ -282,7 +294,6 @@
   private static final Charset CHARSET = Charset.forName("UTF-8");
 
   private static final Map<String, Operation> STRING_TO_OPERATION;
-
   static {
     Map<String, Operation> stringToOperation = new HashMap<String, Operation>();
     for (Operation operation : Operation.values()) {
@@ -291,6 +302,16 @@
     STRING_TO_OPERATION = Collections.unmodifiableMap(stringToOperation);
   }
 
+  private static final Map<String, Acl.InheritanceType> STRING_TO_INHERITANCE_TYPE;
+  static {
+    Map<String, Acl.InheritanceType> stringToType
+        = new HashMap<String, Acl.InheritanceType>();
+    for (Acl.InheritanceType type : Acl.InheritanceType.values()) {
+      stringToType.put(type.getCommonForm(), type);
+    }
+    STRING_TO_INHERITANCE_TYPE = Collections.unmodifiableMap(stringToType);
+  }
+
   private InputStream inputStream;
   private int versionNumber = 0;
   private String delimiter;
@@ -376,7 +397,6 @@
   }
 
   public void readFromRetriever(DocId docId, Response response) throws IOException {
-
     Command command = readCommand();
 
     if (command == null) {
@@ -391,6 +411,14 @@
       throw new IOException("requested document "  + docId + " does not match retrieved "
           + "document " + foundDocId + ".");
     }
+
+    boolean sendAclWithDocument = false;
+    Acl.Builder aclBuilder = new Acl.Builder();
+    DocId inheritFrom = null;  // saves inherit-from in case fragment comes
+    Set<Principal> permits = new TreeSet<Principal>();  // accumulates acl state
+    Set<Principal> denies = new TreeSet<Principal>();  // accumulates acl state
+    String namespace = Principal.DEFAULT_NAMESPACE;  // last given namespace
+
     command = readCommand();
     while (command != null) {
       switch (command.getOperation()) {
@@ -455,12 +483,59 @@
         case LOCK:
           response.setLock(Boolean.parseBoolean(command.getArgument()));
           break;
+        case ACL:
+          sendAclWithDocument = true;
+          break;
+        case NAMESPACE:
+          namespace = command.getArgument();
+          break;
+        case ACL_PERMIT_USER:
+          permits.add(new UserPrincipal(command.getArgument(), namespace));
+          break;
+        case ACL_DENY_USER:
+          denies.add(new UserPrincipal(command.getArgument(), namespace));
+          break;
+        case ACL_PERMIT_GROUP:
+          permits.add(new GroupPrincipal(command.getArgument(), namespace));
+          break;
+        case ACL_DENY_GROUP:
+          denies.add(new GroupPrincipal(command.getArgument(), namespace));
+          break;
+        case ACL_INHERIT_FROM:
+          inheritFrom = new DocId(command.getArgument());
+          aclBuilder.setInheritFrom(inheritFrom);
+          break;
+        case ACL_INHERIT_FRAGMENT:
+          if (null == inheritFrom) {
+            throw new IOException("acl-inherit-fragment cannot preceed acl-inherit-from");
+          }
+          aclBuilder.setInheritFrom(inheritFrom, command.getArgument());
+          break;
+        case ACL_INHERITANCE_TYPE:
+          Acl.InheritanceType type = STRING_TO_INHERITANCE_TYPE.get(command.getArgument());
+          if (null == type) {
+            throw new IOException("invalid acl-inheritance-type: " + command.getArgument());
+          }
+          aclBuilder.setInheritanceType(type);
+          break;
+        case ACL_CASE_SENSITIVE:
+          aclBuilder.setEverythingCaseSensitive();
+          break;
+        case ACL_CASE_INSENSITIVE:
+          aclBuilder.setEverythingCaseInsensitive();
+          break;
         default:
           throw new IOException("Retriever Error: invalid operation: '" + command.getOperation() +
               (command.hasArgument() ? "' with argument: '"  + command.getArgument() + "'" : "'"));
       }
       command = readCommand();
     }
+    // Finish by putting accumulated ACL into response.
+    if (sendAclWithDocument) {
+      aclBuilder.setPermits(permits);
+      aclBuilder.setDenies(denies);
+      response.setAcl(aclBuilder.build());
+    }
   }
 
   /**
@@ -559,7 +634,7 @@
       Operation operation = STRING_TO_OPERATION.get(commandTokens[0]);
       // Skip over unrecognized commands
       if (operation == null) {
-        // TODO(johnfelton) add a warning about an unrecognized command
+        log.warning("Unrecognized command: " + commandTokens[0]);
         continue;
       }
 
diff --git a/test/com/google/enterprise/adaptor/CommandStreamParserTest.java b/test/com/google/enterprise/adaptor/CommandStreamParserTest.java
index 8f02f7a..a60f514 100644
--- a/test/com/google/enterprise/adaptor/CommandStreamParserTest.java
+++ b/test/com/google/enterprise/adaptor/CommandStreamParserTest.java
@@ -200,6 +200,162 @@
   }
 
   @Test
+  public void testRetrieverWithoutAcl() throws IOException {
+    String source = "GSA Adaptor Data Version 1 [\n]\n" +
+        "id=isacltest\n" +
+        "content\npick-up-sticks";
+    InputStream inputStream
+        = new ByteArrayInputStream(source.getBytes("UTF-8"));
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    WrapperAdaptor.GetContentsResponse response
+        = new WrapperAdaptor.GetContentsResponse(outputStream);
+    CommandStreamParser parser = new CommandStreamParser(inputStream);
+    assertEquals(1, parser.getVersionNumber());
+    parser.readFromRetriever(new DocId("isacltest"), response);
+    assertArrayEquals("pick-up-sticks".getBytes(), outputStream.toByteArray());
+    assertEquals(null, response.getAcl());
+    assertEquals(false, response.isSecure());
+  }
+
+  @Test
+  public void testRetrieverNamespaceAloneDoesntMakeAcl() throws IOException {
+    String source = "GSA Adaptor Data Version 1 [\n]\n" +
+        "id=isacltest\n" +
+        "namespace=winds\n" + 
+        "content\npick-up-sticks";
+    InputStream inputStream
+        = new ByteArrayInputStream(source.getBytes("UTF-8"));
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    WrapperAdaptor.GetContentsResponse response
+        = new WrapperAdaptor.GetContentsResponse(outputStream);
+    CommandStreamParser parser = new CommandStreamParser(inputStream);
+    assertEquals(1, parser.getVersionNumber());
+    parser.readFromRetriever(new DocId("isacltest"), response);
+    assertArrayEquals("pick-up-sticks".getBytes(), outputStream.toByteArray());
+    assertEquals(null, response.getAcl());
+    assertEquals(false, response.isSecure());
+  }
+
+  private static GroupPrincipal g(String n, String ns) {
+    return new GroupPrincipal(n, ns);
+  }
+
+  private static GroupPrincipal g(String n) {
+    return new GroupPrincipal(n);
+  }
+
+  private static UserPrincipal u(String n, String ns) {
+    return new UserPrincipal(n, ns);
+  }
+
+  private static UserPrincipal u(String n) {
+    return new UserPrincipal(n);
+  }
+
+  @Test
+  public void testRetrieverSimpleAcl() throws IOException {
+    String source = "GSA Adaptor Data Version 1 [\n]\n" +
+        "id=isacltest\n" +
+        "acl\n" +
+        "acl-permit-user=ted@abc\n" +
+        "acl-deny-group=todd@abc\n" +
+        "namespace=Winds\n" +
+        "acl-permit-user=Banjo@Zephyr\n" +
+        "acl-permit-group=ward@Zephyr\n" +
+        "namespace=RockaRollas\n" +
+        "acl-deny-user=bob.dylan@rocka\n" +
+        "acl-deny-user=paul@rocka\n" +
+        "acl-deny-group=beatles@rocka\n" +
+        "acl-inherit-from=oxfords\n" +
+        "acl-inheritance-type=and-both-permit\n" +
+        "content\npick-up-sticks";
+    InputStream inputStream
+        = new ByteArrayInputStream(source.getBytes("UTF-8"));
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    WrapperAdaptor.GetContentsResponse response
+        = new WrapperAdaptor.GetContentsResponse(outputStream);
+    CommandStreamParser parser = new CommandStreamParser(inputStream);
+    assertEquals(1, parser.getVersionNumber());
+    parser.readFromRetriever(new DocId("isacltest"), response);
+    assertArrayEquals("pick-up-sticks".getBytes(), outputStream.toByteArray());
+    Acl golden = new Acl.Builder().setPermits(Arrays.asList(
+        g("ward@Zephyr", "Winds"), u("ted@abc"), u("Banjo@Zephyr", "Winds")))
+        .setDenies(Arrays.asList(g("todd@abc"), g("beatles@rocka", "RockaRollas"),
+        u("bob.dylan@rocka", "RockaRollas"), u("paul@rocka", "RockaRollas")))
+        .setInheritFrom(new DocId("oxfords"))
+        .setInheritanceType(Acl.InheritanceType.AND_BOTH_PERMIT)
+        .build();
+    assertEquals(golden, response.getAcl());
+  }
+
+  @Test
+  public void testRetrieverInsensitiveAcl() throws IOException {
+    String source = "GSA Adaptor Data Version 1 [\n]\n" +
+        "id=isacltest\n" +
+        "acl\n" +
+        "acl-case-insensitive\n" +
+        "content\npick-up-sticks";
+    InputStream inputStream
+        = new ByteArrayInputStream(source.getBytes("UTF-8"));
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    WrapperAdaptor.GetContentsResponse response
+        = new WrapperAdaptor.GetContentsResponse(outputStream);
+    CommandStreamParser parser = new CommandStreamParser(inputStream);
+    assertEquals(1, parser.getVersionNumber());
+    parser.readFromRetriever(new DocId("isacltest"), response);
+    assertArrayEquals("pick-up-sticks".getBytes(), outputStream.toByteArray());
+    assertEquals(new Acl.Builder().setEverythingCaseInsensitive().build(),
+        response.getAcl());
+  }
+
+  @Test
+  public void testRetrieverLastSensitiveStick() throws IOException {
+    String source = "GSA Adaptor Data Version 1 [\n]\n" +
+        "id=isacltest\n" +
+        "acl\n" +
+        "acl-case-insensitive\n" +
+        "acl-case-sensitive\n" +
+        "acl-case-insensitive\n" +
+        "acl-case-sensitive\n" +
+        "content\npick-up-sticks";
+    InputStream inputStream
+        = new ByteArrayInputStream(source.getBytes("UTF-8"));
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    WrapperAdaptor.GetContentsResponse response
+        = new WrapperAdaptor.GetContentsResponse(outputStream);
+    CommandStreamParser parser = new CommandStreamParser(inputStream);
+    assertEquals(1, parser.getVersionNumber());
+    parser.readFromRetriever(new DocId("isacltest"), response);
+    assertArrayEquals("pick-up-sticks".getBytes(), outputStream.toByteArray());
+    assertEquals(true, response.getAcl().isEverythingCaseSensitive());
+    assertEquals(false, response.getAcl().isEverythingCaseInsensitive());
+  }
+
+  @Test
+  public void testRetrieverAclHasFragment() throws IOException {
+    String source = "GSA Adaptor Data Version 1 [\n]\n" +
+        "id=isacltest\n" +
+        "acl-inherit-from=london\n" +
+        "acl-inherit-from=oxfords\n" +
+        "acl\n" +
+        "acl-inherit-fragment=topleft\n" +
+        "acl-inherit-fragment=lowerright\n" +
+        "content\npick-up-sticks";
+    InputStream inputStream
+        = new ByteArrayInputStream(source.getBytes("UTF-8"));
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    WrapperAdaptor.GetContentsResponse response
+        = new WrapperAdaptor.GetContentsResponse(outputStream);
+    CommandStreamParser parser = new CommandStreamParser(inputStream);
+    assertEquals(1, parser.getVersionNumber());
+    parser.readFromRetriever(new DocId("isacltest"), response);
+    assertArrayEquals("pick-up-sticks".getBytes(), outputStream.toByteArray());
+    assertEquals(new Acl.Builder()
+        .setInheritFrom(new DocId("oxfords"), "lowerright").build(),
+         response.getAcl());
+  }
+
+  @Test
   public void testRetrieverMultipleMetadataValuesSameKey() throws IOException {
     String source = "GSA Adaptor Data Version 1 [\n]\n" +
         "id=123\n" +