Merge pull request #4 from myrlen/develop

Moved EventExpectation and related code from provisioner to lang.
diff --git a/build.gradle b/build.gradle
index 9741575..e6eafe8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -104,5 +104,6 @@
         java = 'SLASHSTAR_STYLE'
     }
     ext.year = Calendar.getInstance().get(Calendar.YEAR)
-    ext.name = 'The Mifos Initiative'
+    ext.name = 'Kuelap, Inc'
+    skipExistingHeaders true
 }
\ No newline at end of file
diff --git a/src/main/java/io/mifos/core/lang/listening/EventExpectation.java b/src/main/java/io/mifos/core/lang/listening/EventExpectation.java
new file mode 100644
index 0000000..1e6f248
--- /dev/null
+++ b/src/main/java/io/mifos/core/lang/listening/EventExpectation.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 Kuelap, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.core.lang.listening;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @author Myrle Krantz
+ */
+public class EventExpectation {
+  private final EventKey key;
+  private boolean eventFound = false;
+  private boolean eventWithdrawn = false;
+
+  private final ReentrantLock lock = new ReentrantLock();
+
+  private final Condition found = lock.newCondition();
+
+  EventExpectation(final EventKey key) {
+    this.key = key;
+  }
+
+  EventKey getKey() {
+    return key;
+  }
+
+  void setEventFound() {
+    lock.lock();
+    try {
+      this.eventFound = true;
+      found.signal();
+    }
+    finally {
+      lock.unlock();
+    }
+  }
+
+  void setEventWithdrawn() {
+    lock.lock();
+    try {
+      this.eventWithdrawn = true;
+      found.signal();
+    }
+    finally {
+      lock.unlock();
+    }
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  public boolean waitForOccurrence(long timeout, TimeUnit timeUnit) throws InterruptedException {
+
+    lock.lock();
+    try {
+      if (eventFound)
+        return true;
+
+      if (eventWithdrawn)
+        return false;
+
+      found.await(timeout, timeUnit);
+
+      return (eventFound);
+    }
+    finally {
+      lock.unlock();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return key.toString();
+  }
+}
diff --git a/src/main/java/io/mifos/core/lang/listening/EventKey.java b/src/main/java/io/mifos/core/lang/listening/EventKey.java
new file mode 100644
index 0000000..9026288
--- /dev/null
+++ b/src/main/java/io/mifos/core/lang/listening/EventKey.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Kuelap, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.core.lang.listening;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("WeakerAccess")
+public class EventKey {
+  private String tenantIdentifier;
+  private String eventName;
+  private Object event;
+
+  public EventKey(
+      final String tenantIdentifier,
+      final String eventName,
+      final Object event) {
+    this.tenantIdentifier = tenantIdentifier;
+    this.eventName = eventName;
+    this.event = event;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    EventKey eventKey = (EventKey) o;
+    return Objects.equals(tenantIdentifier, eventKey.tenantIdentifier) &&
+        Objects.equals(eventName, eventKey.eventName) &&
+        Objects.equals(event, eventKey.event);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(tenantIdentifier,  eventName, event);
+  }
+
+  @Override
+  public String toString() {
+    return "EventKey{" +
+        "tenantIdentifier='" + tenantIdentifier + '\'' +
+        ", eventName='" + eventName + '\'' +
+        ", event=" + event +
+        '}';
+  }
+}
diff --git a/src/main/java/io/mifos/core/lang/listening/TenantedEventListener.java b/src/main/java/io/mifos/core/lang/listening/TenantedEventListener.java
new file mode 100644
index 0000000..9993097
--- /dev/null
+++ b/src/main/java/io/mifos/core/lang/listening/TenantedEventListener.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 Kuelap, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.core.lang.listening;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("WeakerAccess")
+public class TenantedEventListener {
+  private final Map<EventKey, EventExpectation> eventExpectations = new ConcurrentHashMap<>();
+
+  public EventExpectation expect(final EventKey eventKey) {
+    final EventExpectation value = new EventExpectation(eventKey);
+    eventExpectations.put(eventKey, value);
+    return value;
+  }
+
+  public void notify(final EventKey eventKey) {
+    final EventExpectation eventExpectation = eventExpectations.remove(eventKey);
+    if (eventExpectation != null) {
+      eventExpectation.setEventFound();
+    }
+  }
+
+  public void withdrawExpectation(final EventExpectation eventExpectation) {
+    final EventExpectation expectation = eventExpectations.remove(eventExpectation.getKey());
+    if (expectation != null) {
+      eventExpectation.setEventWithdrawn();
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/io/mifos/core/lang/listening/TenantedEventListenerTest.java b/src/test/java/io/mifos/core/lang/listening/TenantedEventListenerTest.java
new file mode 100644
index 0000000..ed919c0
--- /dev/null
+++ b/src/test/java/io/mifos/core/lang/listening/TenantedEventListenerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 Kuelap, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.core.lang.listening;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Myrle Krantz
+ */
+public class TenantedEventListenerTest {
+  static class SomeState {
+    boolean mutableBit = false;
+  }
+
+  @Test
+  public void shouldWaitForEventOccurenceTrue() throws InterruptedException {
+    final TenantedEventListener testSubject = new TenantedEventListener();
+    final EventKey eventKey = new EventKey("shouldWaitForEventOccurenceTrue", "blah", "blah");
+    final EventExpectation eventExpectation = testSubject.expect(eventKey);
+    final SomeState someState = stateThatMutatesIn60MillisThenNotifiesOnEventKey(testSubject, eventKey);
+
+    Assert.assertTrue(eventExpectation.waitForOccurrence(600, TimeUnit.MILLISECONDS));
+    Assert.assertTrue(someState.mutableBit);
+  }
+
+  @Test
+  public void shouldWaitForEventOccurenceFalse() throws InterruptedException {
+    final TenantedEventListener testSubject = new TenantedEventListener();
+    final EventKey eventKey = new EventKey("shouldWaitForEventOccurenceFalse", "blah", "blah");
+    final EventExpectation eventExpectation = testSubject.expect(eventKey);
+    final SomeState someState = stateThatMutatesIn60MillisThenNotifiesOnEventKey(testSubject, eventKey);
+
+    Assert.assertFalse(eventExpectation.waitForOccurrence(6, TimeUnit.MILLISECONDS));
+    Assert.assertFalse(someState.mutableBit); //Some potential for flakiness here, because this is timing dependent.
+  }
+
+  @Test
+  public void shouldNotWaitForEventOccurence() throws InterruptedException {
+    final TenantedEventListener testSubject = new TenantedEventListener();
+    final EventKey eventKey = new EventKey("shouldNotWaitForEventOccurence", "blah", "blah");
+    final EventExpectation eventExpectation = testSubject.expect(eventKey);
+    final SomeState someState = stateThatMutatesIn60MillisThenNotifiesOnEventKey(testSubject, eventKey);
+
+    testSubject.withdrawExpectation(eventExpectation);
+
+    Assert.assertFalse(eventExpectation.waitForOccurrence(600, TimeUnit.MILLISECONDS));
+    Assert.assertFalse(someState.mutableBit); //Some potential for flakiness here, because this is timing dependent.
+  }
+
+  private SomeState stateThatMutatesIn60MillisThenNotifiesOnEventKey(
+      final TenantedEventListener testSubject,
+      final EventKey eventKey) {
+    final SomeState someState = new SomeState();
+    new Thread(() -> {
+      try {
+        TimeUnit.MILLISECONDS.sleep(60);
+        someState.mutableBit = true;
+        testSubject.notify(eventKey);
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+    }).start();
+    return someState;
+  }
+}
\ No newline at end of file