BUG-7611: Add a simple DOMEntityOwnershipService 49/50749/6
authorRobert Varga <rovarga@cisco.com>
Fri, 20 Jan 2017 22:57:50 +0000 (23:57 +0100)
committerRobert Varga <rovarga@cisco.com>
Mon, 23 Jan 2017 16:36:13 +0000 (17:36 +0100)
This adds a very simplistic DOMEntityOwnershipService,
which assumes it is the only thing in town. Local node always
owns the entities registered.

Listeners are dispatched synchronously as registrations occur.

Change-Id: Ia0a4dbcc159d80b4efd2463722867eaf6a791780
Signed-off-by: Robert Varga <rovarga@cisco.com>
common/artifacts/pom.xml
entityownership/mdsal-eos-dom-simple/pom.xml [new file with mode: 0644]
entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java [new file with mode: 0644]
entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java [new file with mode: 0644]
entityownership/pom.xml

index f0bb4689f59ff6cfb95b7573b7893642a1a37102..0c6c4fd561699589cf4cbe6f66b7c04a82b929b2 100644 (file)
     <dependencyManagement>
         <dependencies>
 
-          <dependency>
-            <groupId>org.opendaylight.mdsal</groupId>
-            <artifactId>features-mdsal</artifactId>
-            <classifier>features</classifier>
-            <version>${project.version}</version>
-            <type>xml</type>
-          </dependency>
-
-          <!-- Common APIs for Binding and DOM -->
-          <dependency>
-              <groupId>org.opendaylight.mdsal</groupId>
-              <artifactId>mdsal-common-api</artifactId>
-              <version>${project.version}</version>
-          </dependency>
-
-          <dependency>
-              <groupId>org.opendaylight.mdsal</groupId>
-              <artifactId>mdsal-dom-api</artifactId>
-              <version>${project.version}</version>
-          </dependency>
-          <dependency>
-              <groupId>org.opendaylight.mdsal</groupId>
-              <artifactId>mdsal-dom-spi</artifactId>
-              <version>${project.version}</version>
-          </dependency>
-          <dependency>
-              <groupId>org.opendaylight.mdsal</groupId>
-              <artifactId>mdsal-dom-broker</artifactId>
-              <version>${project.version}</version>
-          </dependency>
-          <dependency>
-              <groupId>org.opendaylight.mdsal</groupId>
-              <artifactId>mdsal-dom-inmemory-datastore</artifactId>
-              <version>${project.version}</version>
-          </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>features-mdsal</artifactId>
+                <classifier>features</classifier>
+                <version>${project.version}</version>
+                <type>xml</type>
+            </dependency>
+
+            <!-- Common APIs for Binding and DOM -->
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-common-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-dom-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-dom-spi</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-dom-broker</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-dom-inmemory-datastore</artifactId>
+                <version>${project.version}</version>
+            </dependency>
 
             <!-- Binding MD-SAL & Java Binding -->
             <dependency>
                 <artifactId>mdsal-eos-dom-api</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-eos-dom-simple</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.opendaylight.mdsal</groupId>
                 <artifactId>mdsal-eos-binding-api</artifactId>
diff --git a/entityownership/mdsal-eos-dom-simple/pom.xml b/entityownership/mdsal-eos-dom-simple/pom.xml
new file mode 100644 (file)
index 0000000..2ccb436
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+
+ This program and the accompanying materials are made available under the
+ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ and is available at http://www.eclipse.org/legal/epl-v10.html
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.opendaylight.odlparent</groupId>
+        <artifactId>bundle-parent</artifactId>
+        <version>1.8.0-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.opendaylight.mdsal</groupId>
+    <artifactId>mdsal-eos-dom-simple</artifactId>
+    <version>2.2.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-artifacts</artifactId>
+                <version>2.2.0-SNAPSHOT</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.yangtools</groupId>
+                <artifactId>yangtools-artifacts</artifactId>
+                <version>1.1.0-SNAPSHOT</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-eos-common-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-eos-common-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-eos-dom-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.yangtools</groupId>
+            <artifactId>yang-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.kohsuke.metainf-services</groupId>
+            <artifactId>metainf-services</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <scm>
+        <connection>scm:git:http://git.opendaylight.org/gerrit/controller.git</connection>
+        <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
+        <tag>HEAD</tag>
+        <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
+    </scm>
+
+  <!--
+      Maven Site Configuration
+
+      The following configuration is necessary for maven-site-plugin to
+      correctly identify the correct deployment path for OpenDaylight Maven
+      sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+</project>
diff --git a/entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java b/entityownership/mdsal-eos-dom-simple/src/main/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipService.java
new file mode 100644 (file)
index 0000000..f88d150
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.eos.dom.simple;
+
+import static org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED;
+import static org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Table;
+import java.util.Collection;
+import java.util.UUID;
+import javax.annotation.concurrent.GuardedBy;
+import org.kohsuke.MetaInfServices;
+import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
+import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState;
+import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
+import org.opendaylight.yangtools.concepts.AbstractObjectRegistration;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple {@link DOMEntityOwnershipService} operating as an isolated island. It has no awareness of the world outside
+ * of itself.
+ *
+ * @author Robert Varga
+ */
+@MetaInfServices
+public final class SimpleDOMEntityOwnershipService implements DOMEntityOwnershipService {
+    private static final Logger LOG = LoggerFactory.getLogger(SimpleDOMEntityOwnershipService.class);
+
+    @GuardedBy("entities")
+    private final Table<String, YangInstanceIdentifier, DOMEntity> entities = HashBasedTable.create();
+
+    @GuardedBy("listeners")
+    private final Multimap<String, DOMEntityOwnershipListener> listeners = ArrayListMultimap.create(0, 1);
+
+    private final UUID uuid;
+
+    @VisibleForTesting
+    SimpleDOMEntityOwnershipService(final UUID uuid) {
+        this.uuid = Preconditions.checkNotNull(uuid);
+    }
+
+    public SimpleDOMEntityOwnershipService() {
+        this(UUID.randomUUID());
+    }
+
+    @Override
+    public DOMEntityOwnershipCandidateRegistration registerCandidate(final DOMEntity entity)
+            throws CandidateAlreadyRegisteredException {
+        synchronized (entities) {
+            final DOMEntity prev = entities.get(entity.getType(), entity.getIdentifier());
+            if (prev != null) {
+                throw new CandidateAlreadyRegisteredException(prev);
+            }
+
+            entities.put(entity.getType(), entity.getIdentifier(), entity);
+            LOG.debug("{}: registered candidate {}", uuid, entity);
+        }
+
+        notifyListeners(entity, LOCAL_OWNERSHIP_GRANTED);
+        return new EntityRegistration(entity);
+    }
+
+    @Override
+    public DOMEntityOwnershipListenerRegistration registerListener(final String entityType,
+            final DOMEntityOwnershipListener listener) {
+
+        final Collection<DOMEntity> owned;
+        synchronized (entities) {
+            owned = ImmutableList.copyOf(entities.row(entityType).values());
+            LOG.trace("{}: acquired candidates {} for new listener {}", uuid, owned, listener);
+        }
+
+        synchronized (listeners) {
+            listeners.put(entityType, listener);
+        }
+
+        for (DOMEntity entity : owned) {
+            notifyListener(listener, new DOMEntityOwnershipChange(entity, LOCAL_OWNERSHIP_GRANTED));
+        }
+        LOG.debug("{}: registered listener {}", uuid, listener);
+        return new ListenerRegistration(entityType, listener);
+    }
+
+    @Override
+    public Optional<EntityOwnershipState> getOwnershipState(final DOMEntity forEntity) {
+        return isCandidateRegistered(forEntity) ? Optional.of(EntityOwnershipState.IS_OWNER) : Optional.absent();
+    }
+
+    @Override
+    public boolean isCandidateRegistered(final DOMEntity forEntity) {
+        synchronized (entities) {
+            return entities.contains(forEntity.getType(), forEntity.getIdentifier());
+        }
+    }
+
+    private void removeEntity(final DOMEntity entity) {
+        synchronized (entities) {
+            entities.remove(entity.getType(), entity.getIdentifier());
+            LOG.debug("{}: unregistered candidate {}", uuid, entity);
+        }
+
+        notifyListeners(entity, LOCAL_OWNERSHIP_LOST_NO_OWNER);
+    }
+
+    @SuppressWarnings("checkstyle:illegalCatch")
+    private void notifyListener(final DOMEntityOwnershipListener listener, final DOMEntityOwnershipChange change) {
+        try {
+            LOG.trace("{} notifying listener {} change {}", uuid, listener, change);
+            listener.ownershipChanged(change);
+        } catch (RuntimeException e) {
+            LOG.warn("{}: Listener {} change {} failed", uuid, listener, change, e);
+        }
+    }
+
+    private void notifyListeners(final DOMEntity entity, final EntityOwnershipChangeState state) {
+        final DOMEntityOwnershipChange change = new DOMEntityOwnershipChange(entity, state);
+
+        final Collection<DOMEntityOwnershipListener> snap;
+
+        synchronized (listeners) {
+            snap = ImmutableList.copyOf(listeners.get(entity.getType()));
+        }
+
+        for (DOMEntityOwnershipListener listener : snap) {
+            notifyListener(listener, change);
+        }
+    }
+
+    void unregisterListener(final ListenerRegistration reg) {
+        synchronized (listeners) {
+            listeners.remove(reg.getEntityType(), reg.getInstance());
+            LOG.debug("{}: unregistered listener {}", uuid, reg.getInstance());
+        }
+    }
+
+    @Override
+    public String toString() {
+        final ToStringHelper h = MoreObjects.toStringHelper(SimpleDOMEntityOwnershipService.class).add("uuid", uuid);
+
+        synchronized (entities) {
+            h.add("entities", entities);
+        }
+        synchronized (listeners) {
+            h.add("listeners", listeners);
+        }
+
+        return h.toString();
+    }
+
+    private final class EntityRegistration extends AbstractObjectRegistration<DOMEntity> implements
+            DOMEntityOwnershipCandidateRegistration {
+        EntityRegistration(final DOMEntity entity) {
+            super(entity);
+        }
+
+        @Override
+        protected void removeRegistration() {
+            removeEntity(getInstance());
+        }
+    }
+
+    private final class ListenerRegistration extends AbstractObjectRegistration<DOMEntityOwnershipListener>
+            implements DOMEntityOwnershipListenerRegistration {
+        private final String entityType;
+
+        ListenerRegistration(final String entityType, final DOMEntityOwnershipListener listener) {
+            super(listener);
+            this.entityType = Preconditions.checkNotNull(entityType);
+        }
+
+        @Override
+        public String getEntityType() {
+            return entityType;
+        }
+
+        @Override
+        protected void removeRegistration() {
+            unregisterListener(this);
+        }
+    }
+}
diff --git a/entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java b/entityownership/mdsal-eos-dom-simple/src/test/java/org/opendaylight/mdsal/eos/dom/simple/SimpleDOMEntityOwnershipServiceTest.java
new file mode 100644 (file)
index 0000000..63e0f01
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.eos.dom.simple;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.common.base.Optional;
+import java.util.UUID;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
+import org.opendaylight.mdsal.eos.common.api.EntityOwnershipChangeState;
+import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistration;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipChange;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
+
+public class SimpleDOMEntityOwnershipServiceTest {
+    private static final String FOO_TYPE = "foo";
+    private static final String BAR_TYPE = "bar";
+
+    private static final DOMEntity FOO_FOO_ENTITY = new DOMEntity(FOO_TYPE, "foo");
+    private static final DOMEntity FOO_BAR_ENTITY = new DOMEntity(FOO_TYPE, "bar");
+
+    private DOMEntityOwnershipService service;
+
+    @Before
+    public void setUp() {
+        service = new SimpleDOMEntityOwnershipService();
+    }
+
+    @Test
+    public void testNonExistingEntity() {
+        assertFalse(service.isCandidateRegistered(FOO_FOO_ENTITY));
+        final Optional<EntityOwnershipState> state = service.getOwnershipState(FOO_FOO_ENTITY);
+        assertNotNull(state);
+        assertFalse(state.isPresent());
+    }
+
+    @Test
+    public void testExistingEntity() throws CandidateAlreadyRegisteredException {
+        final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(FOO_FOO_ENTITY);
+        assertNotNull(reg);
+
+        assertTrue(service.isCandidateRegistered(FOO_FOO_ENTITY));
+        assertFalse(service.isCandidateRegistered(FOO_BAR_ENTITY));
+
+        final Optional<EntityOwnershipState> state = service.getOwnershipState(FOO_FOO_ENTITY);
+        assertNotNull(state);
+        assertTrue(state.isPresent());
+        assertEquals(EntityOwnershipState.IS_OWNER, state.get());
+
+        reg.close();
+        assertFalse(service.isCandidateRegistered(FOO_FOO_ENTITY));
+    }
+
+    @Test(expected = CandidateAlreadyRegisteredException.class)
+    public void testDuplicateRegistration() throws CandidateAlreadyRegisteredException {
+        final DOMEntityOwnershipCandidateRegistration reg = service.registerCandidate(FOO_FOO_ENTITY);
+        assertNotNull(reg);
+
+        // Should throw
+        service.registerCandidate(FOO_FOO_ENTITY);
+    }
+
+    @Test
+    public void testListener() throws CandidateAlreadyRegisteredException {
+        final DOMEntityOwnershipCandidateRegistration entityReg = service.registerCandidate(FOO_FOO_ENTITY);
+        assertNotNull(entityReg);
+
+        // Mismatched type, not triggered
+        final DOMEntityOwnershipListener barListener = mock(DOMEntityOwnershipListener.class);
+        doNothing().when(barListener).ownershipChanged(any(DOMEntityOwnershipChange.class));
+        final DOMEntityOwnershipListenerRegistration barReg = service.registerListener(BAR_TYPE, barListener);
+        verifyZeroInteractions(barListener);
+
+        // Matching type should be triggered
+        final DOMEntityOwnershipListener fooListener = mock(DOMEntityOwnershipListener.class);
+        doNothing().when(fooListener).ownershipChanged(any(DOMEntityOwnershipChange.class));
+        final DOMEntityOwnershipListenerRegistration fooReg = service.registerListener(FOO_TYPE, fooListener);
+        final ArgumentCaptor<DOMEntityOwnershipChange> fooCaptor = ArgumentCaptor.forClass(
+            DOMEntityOwnershipChange.class);
+        verify(fooListener).ownershipChanged(fooCaptor.capture());
+
+        DOMEntityOwnershipChange fooChange = fooCaptor.getValue();
+        assertEquals(FOO_FOO_ENTITY, fooChange.getEntity());
+        assertEquals(EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED, fooChange.getState());
+
+        reset(fooListener);
+        doNothing().when(fooListener).ownershipChanged(any(DOMEntityOwnershipChange.class));
+        entityReg.close();
+        verifyZeroInteractions(barListener);
+        verify(fooListener).ownershipChanged(fooCaptor.capture());
+        fooChange = fooCaptor.getValue();
+        assertEquals(FOO_FOO_ENTITY, fooChange.getEntity());
+        assertEquals(EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER, fooChange.getState());
+
+        fooReg.close();
+        barReg.close();
+    }
+
+    @Test
+    public void testToString() throws CandidateAlreadyRegisteredException {
+        final UUID uuid = UUID.randomUUID();
+        final String expected = String.format("SimpleDOMEntityOwnershipService{uuid=%s, entities={}, listeners={}}",
+            uuid);
+        assertEquals(expected, new SimpleDOMEntityOwnershipService(uuid).toString());
+    }
+}
index df0830f5cec929578644898978bf83ba7128da52..341ec9d0f1d3fe2e9cb44d64bf5aa53427b13882 100644 (file)
@@ -28,6 +28,7 @@
       <module>mdsal-eos-common-api</module>
       <module>mdsal-eos-common-spi</module>
       <module>mdsal-eos-dom-api</module>
+      <module>mdsal-eos-dom-simple</module>
       <module>mdsal-eos-binding-api</module>
       <module>mdsal-eos-binding-adapter</module>
     </modules>