Merge pull request #10 from myrle-krantz/develop

Various changes necessary to get rhythm working within a system of multiple microservices.
diff --git a/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java b/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java
index 83550b4..0af10b2 100644
--- a/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java
+++ b/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java
@@ -29,6 +29,7 @@
 import io.mifos.rhythm.api.v1.events.EventConstants;
 import io.mifos.rhythm.service.config.RhythmConfiguration;
 import io.mifos.rhythm.service.internal.service.BeatPublisherService;
+import io.mifos.rhythm.spi.v1.PermittableGroupIds;
 import org.junit.*;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
@@ -36,6 +37,9 @@
 import org.mockito.AdditionalMatchers;
 import org.mockito.Matchers;
 import org.mockito.Mockito;
+import org.mockito.internal.stubbing.answers.Returns;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -55,6 +59,7 @@
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author Myrle Krantz
@@ -139,25 +144,53 @@
     }
   }
 
-  Beat createBeat(final String applicationIdentifier, final String beatIdentifier) throws InterruptedException {
-    final String tenantIdentifier = tenantDataStoreContext.getTenantName();
+  Beat createBeatForThisHour(final String applicationIdentifier, final String beatIdentifier) throws InterruptedException {
     final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
+    int alignmentHour = now.getHour();
+    final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, alignmentHour);
+    final Beat ret = createBeat(applicationIdentifier, beatIdentifier, alignmentHour, expectedBeatTimestamp);
+
+    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).publishBeat(beatIdentifier, tenantDataStoreContext.getTenantName(), applicationIdentifier, expectedBeatTimestamp);
+
+    return ret;
+  }
+
+  static class AnswerWithDelay<T> implements Answer<T> {
+    private final int sleepyTime;
+    private final Answer<T> answer;
+
+    AnswerWithDelay(final int sleepyTime, final Answer<T> answer) {
+      this.sleepyTime = sleepyTime;
+      this.answer = answer;
+    }
+
+    @Override
+    public T answer(final InvocationOnMock invocation) throws Throwable {
+      TimeUnit.MILLISECONDS.sleep(sleepyTime);
+      return answer.answer(invocation);
+    }
+  }
+
+  Beat createBeat(
+          final String applicationIdentifier,
+          final String beatIdentifier,
+          final int alignmentHour,
+          final LocalDateTime expectedBeatTimestamp) throws InterruptedException {
+    final String tenantIdentifier = tenantDataStoreContext.getTenantName();
 
     final Beat beat = new Beat();
     beat.setIdentifier(beatIdentifier);
-    beat.setAlignmentHour(now.getHour());
+    beat.setAlignmentHour(alignmentHour);
 
-    final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, beat.getAlignmentHour());
-    Mockito.doReturn(Optional.of("boop")).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
-    Mockito.doReturn(true).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier),
+    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(Optional.of(PermittableGroupIds.forApplication(applicationIdentifier))))).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
+    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(true))).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier),
             AdditionalMatchers.or(Matchers.eq(expectedBeatTimestamp), Matchers.eq(getNextTimeStamp(expectedBeatTimestamp))));
 
     this.testSubject.createBeat(applicationIdentifier, beat);
 
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));
 
-    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).requestPermissionForBeats(tenantIdentifier, applicationIdentifier);
-    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).publishBeat(beatIdentifier, tenantDataStoreContext.getTenantName(), applicationIdentifier, expectedBeatTimestamp);
+    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_500).times(1)).requestPermissionForBeats(tenantIdentifier, applicationIdentifier);
 
     return beat;
   }
diff --git a/component-test/src/main/java/io/mifos/rhythm/TestBeats.java b/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
index e0e8409..1f57da7 100644
--- a/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
+++ b/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
@@ -26,6 +26,8 @@
 
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -37,7 +39,7 @@
   @Test
   public void shouldCreateBeat() throws InterruptedException {
     final String applicationIdentifier = "funnybusiness-v1";
-    final Beat beat = createBeat(applicationIdentifier, "bebopthedowop");
+    final Beat beat = createBeatForThisHour(applicationIdentifier, "bebopthedowop");
 
     final Beat createdBeat = this.testSubject.getBeat(applicationIdentifier, beat.getIdentifier());
     Assert.assertEquals(beat, createdBeat);
@@ -50,7 +52,7 @@
   public void shouldDeleteBeat() throws InterruptedException {
     final String applicationIdentifier = "funnybusiness-v2";
 
-    final Beat beat = createBeat(applicationIdentifier, "bebopthedowop");
+    final Beat beat = createBeatForThisHour(applicationIdentifier, "bebopthedowop");
 
     testSubject.deleteBeat(applicationIdentifier, beat.getIdentifier());
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));
@@ -68,7 +70,7 @@
   @Test
   public void shouldDeleteApplication() throws InterruptedException {
     final String applicationIdentifier = "funnybusiness-v3";
-    createBeat(applicationIdentifier, "bebopthedowop");
+    createBeatForThisHour(applicationIdentifier, "bebopthedowop");
 
     this.testSubject.deleteApplication(applicationIdentifier);
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.DELETE_APPLICATION, applicationIdentifier));
@@ -100,4 +102,24 @@
 
     Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(10_000).times(3)).publishBeat(beatId, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp);
   }
+
+  @Test
+  public void twentyFourBeats() throws InterruptedException {
+    final String applicationIdentifier = "funnybusiness-v5";
+    final LocalDateTime today = LocalDateTime.now(ZoneId.of("UTC")).truncatedTo(ChronoUnit.DAYS);
+    final List<Beat> beats = new ArrayList<>();
+    for (int i = 0; i < 24; i ++) {
+      final LocalDateTime expectedBeatTimestamp = today.plusHours(i);
+      beats.add(createBeat(applicationIdentifier, "bebopthedowop" + i, i, expectedBeatTimestamp));
+    }
+
+    beats.forEach(x -> {
+      final Beat createdBeat = this.testSubject.getBeat(applicationIdentifier, x.getIdentifier());
+      Assert.assertEquals(x, createdBeat);
+    });
+
+    final List<Beat> allEntities = this.testSubject.getAllBeatsForApplication(applicationIdentifier);
+
+    beats.forEach(x -> Assert.assertTrue(allEntities.contains(x)));
+  }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/CreateBeatCommand.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/CreateBeatCommand.java
index b5bda4d..e639997 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/CreateBeatCommand.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/CreateBeatCommand.java
@@ -45,4 +45,13 @@
   public Beat getInstance() {
     return this.instance;
   }
+
+  @Override
+  public String toString() {
+    return "CreateBeatCommand{" +
+            "tenantIdentifier='" + tenantIdentifier + '\'' +
+            ", applicationIdentifier='" + applicationIdentifier + '\'' +
+            ", instance=" + instance.getIdentifier() +
+            '}';
+  }
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteApplicationCommand.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteApplicationCommand.java
index af57134..3e4d725 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteApplicationCommand.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteApplicationCommand.java
@@ -36,4 +36,12 @@
   public String getApplicationIdentifier() {
     return this.applicationIdentifier;
   }
+
+  @Override
+  public String toString() {
+    return "DeleteApplicationCommand{" +
+            "tenantIdentifier='" + tenantIdentifier + '\'' +
+            ", applicationIdentifier='" + applicationIdentifier + '\'' +
+            '}';
+  }
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteBeatCommand.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteBeatCommand.java
index bc3ca09..9d1a48c 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteBeatCommand.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/DeleteBeatCommand.java
@@ -45,4 +45,12 @@
     return identifier;
   }
 
+  @Override
+  public String toString() {
+    return "DeleteBeatCommand{" +
+            "tenantIdentifier='" + tenantIdentifier + '\'' +
+            ", applicationIdentifier='" + applicationIdentifier + '\'' +
+            ", identifier='" + identifier + '\'' +
+            '}';
+  }
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/InitializeServiceCommand.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/InitializeServiceCommand.java
index 95aa05e..b2ad4b1 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/InitializeServiceCommand.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/InitializeServiceCommand.java
@@ -23,4 +23,9 @@
   public InitializeServiceCommand() {
     super();
   }
+
+  @Override
+  public String toString() {
+    return "InitializeServiceCommand{}";
+  }
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ApplicationCommandHandler.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ApplicationCommandHandler.java
index 436c34b..9876448 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ApplicationCommandHandler.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ApplicationCommandHandler.java
@@ -17,6 +17,7 @@
 
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
 import io.mifos.rhythm.api.v1.events.EventConstants;
 import io.mifos.rhythm.service.internal.command.DeleteApplicationCommand;
 import io.mifos.rhythm.service.internal.repository.ApplicationRepository;
@@ -45,7 +46,7 @@
     this.eventHelper = eventHelper;
   }
 
-  @CommandHandler
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @Transactional
   public void process(final DeleteApplicationCommand deleteApplicationCommand) {
     this.applicationRepository.deleteByTenantIdentifierAndApplicationIdentifier(deleteApplicationCommand.getTenantIdentifier(), deleteApplicationCommand.getApplicationIdentifier());
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java
index 9c6f7a3..e0bf47b 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java
@@ -17,6 +17,7 @@
 
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
 import io.mifos.core.lang.ServiceException;
 import io.mifos.rhythm.api.v1.events.BeatEvent;
 import io.mifos.rhythm.api.v1.events.EventConstants;
@@ -58,14 +59,26 @@
     this.logger = logger;
   }
 
-  @CommandHandler
-  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.NONE)
   public void process(final CreateBeatCommand createBeatCommand) {
+    processCreateBeatCommand(createBeatCommand);
+
+    final BeatEvent event
+            = new BeatEvent(createBeatCommand.getApplicationIdentifier(), createBeatCommand.getInstance().getIdentifier());
+    logger.info("Sending event {}", event);
+    eventHelper.sendEvent(EventConstants.POST_BEAT, createBeatCommand.getTenantIdentifier(), event);
+  }
+
+  //I want the transaction to close before I send a beat or log it being sent.  So I need a separate function for the
+  //stuff that should happen in the transaction.
+  @SuppressWarnings("WeakerAccess")
+  @Transactional
+  public void processCreateBeatCommand(CreateBeatCommand createBeatCommand) {
     final boolean applicationHasRequestForAccessPermission = identityPermittableGroupService.checkThatApplicationHasRequestForAccessPermission(
             createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationIdentifier());
     if (!applicationHasRequestForAccessPermission) {
-      logger.warn("Rhythm needs permission to publish beats to application, but couldn't request that permission for tenant '{}' and application '{}'.",
-              createBeatCommand.getApplicationIdentifier(), createBeatCommand.getTenantIdentifier());
+      logger.info("Rhythm needs permission to publish beats to application, but couldn't request that permission for tenant '{}' and application '{}'.",
+              createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationIdentifier());
     }
 
     final BeatEntity entity = BeatMapper.map(
@@ -73,12 +86,9 @@
             createBeatCommand.getApplicationIdentifier(),
             createBeatCommand.getInstance());
     this.beatRepository.save(entity);
-
-    eventHelper.sendEvent(EventConstants.POST_BEAT, createBeatCommand.getTenantIdentifier(),
-            new BeatEvent(createBeatCommand.getApplicationIdentifier(), createBeatCommand.getInstance().getIdentifier()));
   }
 
-  @CommandHandler
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.NONE)
   @Transactional
   public void process(final DeleteBeatCommand deleteBeatCommand) {
     final Optional<BeatEntity> toDelete = this.beatRepository.findByTenantIdentifierAndApplicationIdentifierAndBeatIdentifier(
@@ -87,8 +97,8 @@
             deleteBeatCommand.getIdentifier());
     final BeatEntity toDeleteForReal
             = toDelete.orElseThrow(() -> ServiceException.notFound(
-                    "Beat with for the application " + deleteBeatCommand.getApplicationIdentifier() +
-                            ", and the identifier " + deleteBeatCommand.getIdentifier() + " not found."));
+                    "Beat for the application ''" + deleteBeatCommand.getApplicationIdentifier() +
+                            "'' with the identifier ''" + deleteBeatCommand.getIdentifier() + "'' not found."));
 
     this.beatRepository.delete(toDeleteForReal);
 
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/InitializeCommandHandler.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/InitializeCommandHandler.java
index 006b032..5e7fdfc 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/InitializeCommandHandler.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/InitializeCommandHandler.java
@@ -17,14 +17,12 @@
 
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
 import io.mifos.core.command.annotation.EventEmitter;
 import io.mifos.core.mariadb.domain.FlywayFactoryBean;
 import io.mifos.rhythm.api.v1.events.EventConstants;
-import io.mifos.rhythm.service.ServiceConstants;
 import io.mifos.rhythm.service.internal.command.InitializeServiceCommand;
-import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.sql.DataSource;
@@ -36,25 +34,22 @@
 @Aggregate
 public class InitializeCommandHandler {
 
-  private final Logger logger;
   private final DataSource dataSource;
   private final FlywayFactoryBean flywayFactoryBean;
 
+  @SuppressWarnings("SpringJavaAutowiringInspection")
   @Autowired
-  public InitializeCommandHandler(@Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger,
-                                  final DataSource dataSource,
+  public InitializeCommandHandler(final DataSource dataSource,
                                   final FlywayFactoryBean flywayFactoryBean) {
     super();
-    this.logger = logger;
     this.dataSource = dataSource;
     this.flywayFactoryBean = flywayFactoryBean;
   }
 
-  @CommandHandler
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @Transactional
   @EventEmitter(selectorName = EventConstants.SELECTOR_NAME, selectorValue = EventConstants.INITIALIZE)
   public String initialize(final InitializeServiceCommand initializeServiceCommand) {
-    this.logger.debug("Start service migration.");
     this.flywayFactoryBean.create(this.dataSource).migrate();
     return EventConstants.INITIALIZE;
   }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/identity/ApplicationPermissionRequestCreator.java b/service/src/main/java/io/mifos/rhythm/service/internal/identity/ApplicationPermissionRequestCreator.java
index 498638b..ffce545 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/identity/ApplicationPermissionRequestCreator.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/identity/ApplicationPermissionRequestCreator.java
@@ -25,9 +25,12 @@
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 
+import javax.validation.Valid;
+
 /**
  * @author Myrle Krantz
  */
@@ -40,5 +43,6 @@
           produces = {MediaType.ALL_VALUE})
   @ThrowsException(status = HttpStatus.CONFLICT, exception = ApplicationPermissionAlreadyExistsException.class)
   @Permittable(groupId = io.mifos.identity.api.v1.PermittableGroupIds.APPLICATION_SELF_MANAGEMENT)
-  void createApplicationPermission(@PathVariable("applicationidentifier") String applicationIdentifier, Permission permission);
+  void createApplicationPermission(@PathVariable("applicationidentifier") String applicationIdentifier,
+                                   @RequestBody @Valid Permission permission);
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java
index cc2e26f..a070b20 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java
@@ -89,6 +89,8 @@
   public Optional<String> requestPermissionForBeats(final String tenantIdentifier, final String applicationIdentifier) {
     try (final AutoTenantContext ignored = new AutoTenantContext(tenantIdentifier)) {
       try (final AutoUserContext ignored2 = new AutoUserContext(properties.getUser(), "")) {
+        logger.info("Requesting permission to send beats to application '{}' under tenant '{}'.", applicationIdentifier, tenantIdentifier);
+
         final String consumerPermittableGroupIdentifier = PermittableGroupIds.forApplication(applicationIdentifier);
         final Permission publishBeatPermission = new Permission();
         publishBeatPermission.setAllowedOperations(Collections.singleton(AllowedOperation.CHANGE));
@@ -96,12 +98,16 @@
         try {
           applicationPermissionRequestCreator.createApplicationPermission(rhythmApplicationName.toString(), publishBeatPermission);
         }
-        catch (final ApplicationPermissionAlreadyExistsException ignored3) { }
+        catch (final ApplicationPermissionAlreadyExistsException e) {
+          logger.debug("Failed to request permission for application {}, in tenant {} because the request already exists. {} was thrown.", applicationIdentifier, tenantIdentifier, e);
+        }
 
+        logger.debug("Successfully requested permission to send beats to application '{}' under tenant '{}'.", applicationIdentifier, tenantIdentifier);
         return Optional.of(consumerPermittableGroupIdentifier);
       }
     }
     catch (final Throwable e) {
+      logger.warn("Failed to request permission for application {}, in tenant {}.", applicationIdentifier, tenantIdentifier, e);
       return Optional.empty();
     }
   }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java
index d73a5bc..88a21ca 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java
@@ -59,7 +59,9 @@
 
   @Scheduled(initialDelayString = "${rhythm.beatCheckRate}", fixedRateString = "${rhythm.beatCheckRate}")
   @Transactional
-  public void checkForBeatsNeeded() {
+  public synchronized void checkForBeatsNeeded() {
+    //In it's current form this function cannot be run in multiple instances of the same service.  We need to get
+    //locking on selected entries corrected here, before this will work.
     try {
       final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
       //Get beats from the last two hours in case restart/start happens close to hour begin.
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java
index 3ae84b3..441def7 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java
@@ -18,7 +18,9 @@
 import io.mifos.rhythm.service.internal.repository.ApplicationEntity;
 import io.mifos.rhythm.service.internal.repository.ApplicationRepository;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Optional;
@@ -39,10 +41,20 @@
     this.beatPublisherService = beatPublisherService;
   }
 
-  @Transactional
-  public boolean checkThatApplicationHasRequestForAccessPermission(
+  public synchronized boolean checkThatApplicationHasRequestForAccessPermission(
           final String tenantIdentifier,
           final String applicationIdentifier) {
+    try {
+      return checkThatApplicationHasRequestForAccessPermissionHelper(tenantIdentifier, applicationIdentifier);
+    }
+    catch (final DataIntegrityViolationException e) {
+      return false;
+    }
+  }
+
+  @SuppressWarnings("WeakerAccess")
+  @Transactional(propagation = Propagation.REQUIRES_NEW)
+  public boolean checkThatApplicationHasRequestForAccessPermissionHelper(String tenantIdentifier, String applicationIdentifier) {
     final Optional<ApplicationEntity> findApplication = applicationRepository.findByTenantIdentifierAndApplicationIdentifier(
             tenantIdentifier,
             applicationIdentifier);
diff --git a/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java b/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java
index 0cbb554..a795a8b 100644
--- a/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java
+++ b/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java
@@ -84,7 +84,7 @@
             .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + applicationIdentifier + " doesn't exist."));
   }
 
-  @Permittable(value = AcceptedTokenType.SYSTEM, acceptTokenIntendedForForeignApplication = true) //Allow apps to use this endpoint in their provisioning code.
+  @Permittable(value = AcceptedTokenType.SYSTEM, permittedEndpoint = "/applications/{applicationidentifier}/beats", acceptTokenIntendedForForeignApplication = true) //Allow apps to use this endpoint in their provisioning code.
   @RequestMapping(
           method = RequestMethod.POST,
           consumes = MediaType.APPLICATION_JSON_VALUE,
diff --git a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
index 3b5d9d3..bf8e423 100644
--- a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
+++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
@@ -19,11 +19,11 @@
 
 CREATE TABLE khepri_beats (
   id BIGINT NOT NULL AUTO_INCREMENT,
-  tenant_identifier        VARCHAR(32) NOT NULL,
-  application_identifier         VARCHAR(64) NOT NULL,
-  beat_identifier          VARCHAR(32) NOT NULL,
-  alignment_hour           INT         NOT NULL,
-  next_beat               TIMESTAMP(3) NOT NULL,
+  tenant_identifier        VARCHAR(32)  NOT NULL,
+  application_identifier   VARCHAR(64)  NOT NULL,
+  beat_identifier          VARCHAR(32)  NOT NULL,
+  alignment_hour           INT          NOT NULL,
+  next_beat                TIMESTAMP(3) NOT NULL,
   CONSTRAINT khepri_beats_uq UNIQUE (tenant_identifier, application_identifier, beat_identifier),
   CONSTRAINT khepri_beats_pk PRIMARY KEY (id)
 );
@@ -31,7 +31,7 @@
 CREATE TABLE khepri_apps (
   id BIGINT NOT NULL AUTO_INCREMENT,
   tenant_identifier        VARCHAR(32) NOT NULL,
-  application_identifier         VARCHAR(32) NOT NULL,
+  application_identifier   VARCHAR(32) NOT NULL,
   permittable_identifier   VARCHAR(32) NOT NULL,
   CONSTRAINT khepri_apps_uq UNIQUE (tenant_identifier, application_identifier),
   CONSTRAINT khepri_apps_pk PRIMARY KEY (id)