b/162949100 Define Callback Traversal

Change-Id: Ie53c4641e424fa3e5d39c6c1016988236cfc4758
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java
index 265431f..71e3a01 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelper.java
@@ -7,6 +7,7 @@
 import java.util.Optional;
 import javax.inject.Inject;
 import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Callback;
 import org.openapi4j.parser.model.v3.Contact;
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Info;
@@ -44,6 +45,17 @@
   }
 
   @Override
+  public void sendCallbackTraversals(ImmutableMap<String, Callback> callbackMap) {
+    Optional.ofNullable(callbackMap)
+        .ifPresent(
+            callbacks ->
+                callbackMap.forEach(
+                    (name, callback) ->
+                        coordinator.handleTraversalCommand(
+                            factory.create(callback, traversalPath, name))));
+  }
+
+  @Override
   public void sendContactTraversal(Contact contact) {
     Optional.ofNullable(contact)
         .ifPresent(
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java
new file mode 100644
index 0000000..ca89f3c
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversal.java
@@ -0,0 +1,34 @@
+package com.apigee.security.oas.extendedvalidator;
+
+import com.google.common.collect.ImmutableList;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Map;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Callback;
+
+/** A {@link Traversal} that traverses a {@link Callback} object. */
+public class CallbackTraversal extends Traversal<Callback> implements CallbackTraversalCommand {
+  @Inject
+  CallbackTraversal(
+      TraversalHelperFactory traversalHelperFactory,
+      ExtensionValidationIntegrator extensionValidationIntegrator,
+      @Assisted
+          ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      @Assisted Callback openApiSchemaObj,
+      @Assisted String name) {
+    super(
+        traversalHelperFactory,
+        extensionValidationIntegrator,
+        traversalPath,
+        openApiSchemaObj,
+        name);
+  }
+
+  @Override
+  public void traverse() {
+    // TODO(b/161441872): Process extensions.
+    // TODO(b/162508047): Add path object traversal and callback object traversal logic.
+  }
+}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversalCommand.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversalCommand.java
new file mode 100644
index 0000000..fdd48d1
--- /dev/null
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/CallbackTraversalCommand.java
@@ -0,0 +1,4 @@
+package com.apigee.security.oas.extendedvalidator;
+
+/** A {@link TraversalCommand} interface for {@link CallbackTraversal}. */
+public interface CallbackTraversalCommand extends TraversalCommand {}
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java
index c929fa8..6fd6f9f 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/OperationTraversal.java
@@ -34,7 +34,8 @@
     TraversalHelper traversalHelper = traversalHelperFactory.create(getUpdatedTraversalPath());
 
     traversalHelper.sendExternalDocsTraversal(openApiSchemaObj.getExternalDocs());
-    // TODO(b/162949100): Send Callback Traversals.
+    traversalHelper.sendCallbackTraversals(
+        Immutables.toImmutableMap(openApiSchemaObj.getCallbacks()));
     traversalHelper.sendServerTraversals(Immutables.toImmutableList(openApiSchemaObj.getServers()));
     traversalHelper.sendParameterTraversals(
         Immutables.toImmutableList(openApiSchemaObj.getParameters()));
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java
index ac98888..5d7f241 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactory.java
@@ -4,6 +4,7 @@
 import java.util.Map;
 import java.util.Optional;
 import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Callback;
 import org.openapi4j.parser.model.v3.Contact;
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Info;
@@ -22,6 +23,11 @@
  */
 interface TraversalCommandFactory {
 
+  CallbackTraversalCommand create(
+      Callback callback,
+      ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath,
+      String name);
+
   ContactTraversalCommand create(
       Contact contact,
       ImmutableList<Map.Entry<Class<? extends OpenApiSchema>, Optional<String>>> traversalPath);
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java
index b0e6c56..dfc7cb9 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryModule.java
@@ -9,6 +9,7 @@
   protected void configure() {
     install(
         new FactoryModuleBuilder()
+            .implement(CallbackTraversalCommand.class, CallbackTraversal.class)
             .implement(ContactTraversalCommand.class, ContactTraversal.class)
             .implement(ExternalDocsTraversalCommand.class, ExternalDocsTraversal.class)
             .implement(InfoTraversalCommand.class, InfoTraversal.class)
diff --git a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java
index ca55a12..776c07d 100644
--- a/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java
+++ b/oas-core/src/main/java/com/apigee/security/oas/extendedvalidator/TraversalHelper.java
@@ -2,6 +2,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import org.openapi4j.parser.model.v3.Callback;
 import org.openapi4j.parser.model.v3.Contact;
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Info;
@@ -17,6 +18,8 @@
 /** A set of methods to handle the building and sending of {@linkplain Traversal Traversals}. */
 interface TraversalHelper {
 
+  void sendCallbackTraversals(ImmutableMap<String, Callback> callbackMap);
+
   void sendContactTraversal(Contact contact);
 
   void sendExternalDocsTraversal(ExternalDocs externalDocs);
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java
index f42f992..4a5e6c5 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/BaseTraversalHelperTest.java
@@ -16,6 +16,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
+import org.openapi4j.parser.model.v3.Callback;
 import org.openapi4j.parser.model.v3.Contact;
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Info;
@@ -43,6 +44,8 @@
   private final ImmutableList<Tag> tags =
       ImmutableList.of(new Tag(), new Tag(), new Tag(), new Tag());
 
+  private final ImmutableMap<String, Callback> callbacks =
+      ImmutableMap.of("one", new Callback(), "two", new Callback(), "three", new Callback());
   private final ImmutableMap<String, ServerVariable> serverVariables =
       ImmutableMap.of(
           "one", new ServerVariable(), "two", new ServerVariable(), "three", new ServerVariable());
@@ -66,6 +69,19 @@
   }
 
   @Test
+  public void sendCallbackTraversals_sendsCallbackTraversalCommandsToCoordinator() {
+    traversalHelper.sendCallbackTraversals(callbacks);
+
+    verify(traversalCoordinator, times(callbacks.size()))
+        .handleTraversalCommand(any(CallbackTraversalCommand.class));
+  }
+
+  @Test
+  public void sendCallbackTraversals_nullParameter_doesNotFail() {
+    traversalHelper.sendCallbackTraversals(null);
+  }
+
+  @Test
   public void sendContactTraversal_sendsContactTraversalCommandToCoordinator() {
     traversalHelper.sendContactTraversal(contact);
 
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java
index eca900f..6a5c9af 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/OperationTraversalTest.java
@@ -16,6 +16,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
+import org.openapi4j.parser.model.v3.Callback;
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Operation;
 import org.openapi4j.parser.model.v3.Parameter;
@@ -33,12 +34,15 @@
   @Mock private Operation operation;
 
   private final ExternalDocs externalDocs = new ExternalDocs();
+
   private final ImmutableList servers = ImmutableList.of(new Server(), new Server(), new Server());
 
   private final ImmutableList parameters =
       ImmutableList.of(new Parameter(), new Parameter(), new Parameter(), new Parameter());
   private final ImmutableMap responses =
       ImmutableMap.of("one", new Response(), "two", new Response());
+  private final ImmutableMap callbacks =
+      ImmutableMap.of("one", new Callback(), "two", new Callback(), "three", new Callback());
 
   private OperationTraversal operationTraversal;
 
@@ -50,6 +54,7 @@
     when(operation.getServers()).thenReturn(servers);
     when(operation.getParameters()).thenReturn(parameters);
     when(operation.getResponses()).thenReturn(responses);
+    when(operation.getCallbacks()).thenReturn(callbacks);
 
     operationTraversal =
         new OperationTraversal(
@@ -116,4 +121,20 @@
 
     verify(traversalHelper, atLeastOnce()).sendServerTraversals(any());
   }
+
+  @Test
+  public void traverse_sendsCallbacksToTraversalHelper() {
+    operationTraversal.traverse();
+
+    verify(traversalHelper).sendCallbackTraversals(callbacks);
+  }
+
+  @Test
+  public void traverse_nullCallbacksMember_doesNotFail() {
+    when(operation.getCallbacks()).thenReturn(null);
+
+    operationTraversal.traverse();
+
+    verify(traversalHelper, atLeastOnce()).sendCallbackTraversals(any());
+  }
 }
diff --git a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java
index baa2e8b..3a31349 100644
--- a/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java
+++ b/oas-core/src/test/java/com/apigee/security/oas/extendedvalidator/TraversalCommandFactoryTest.java
@@ -11,6 +11,7 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.openapi4j.parser.model.OpenApiSchema;
+import org.openapi4j.parser.model.v3.Callback;
 import org.openapi4j.parser.model.v3.Contact;
 import org.openapi4j.parser.model.v3.ExternalDocs;
 import org.openapi4j.parser.model.v3.Info;
@@ -33,6 +34,11 @@
       traversalPath = ImmutableList.copyOf(new ArrayDeque<>());
 
   @Test
+  public void create_callbackAndNamePassed_returnsNonNullObject() {
+    assertThat(traversalCommandFactory.create(new Callback(), traversalPath, "name")).isNotNull();
+  }
+
+  @Test
   public void create_contactPassed_returnsNonNullObject() {
     assertThat(traversalCommandFactory.create(new Contact(), traversalPath)).isNotNull();
   }