Created clockoffset endpoint and persisted per tenant.
diff --git a/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java b/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java
index 4b10d4f..a4f0981 100644
--- a/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java
+++ b/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java
@@ -17,6 +17,7 @@
import io.mifos.core.api.util.CustomFeignClientsConfiguration;
import io.mifos.rhythm.api.v1.domain.Beat;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
@@ -31,6 +32,21 @@
@SuppressWarnings("unused")
@FeignClient(value="rhythm-v1", path="/rhythm/v1", configuration = CustomFeignClientsConfiguration.class)
public interface RhythmManager {
+ @RequestMapping(
+ value = "/clockoffset",
+ method = RequestMethod.PUT,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ void setClockOffset(ClockOffset clockOffset);
+
+ @RequestMapping(
+ value = "/clockoffset",
+ method = RequestMethod.GET,
+ produces = MediaType.APPLICATION_JSON_VALUE,
+ consumes = MediaType.APPLICATION_JSON_VALUE
+ )
+ ClockOffset getClockOffset();
@RequestMapping(
value = "/applications/{applicationidentifier}",
diff --git a/api/src/main/java/io/mifos/rhythm/api/v1/domain/ClockOffset.java b/api/src/main/java/io/mifos/rhythm/api/v1/domain/ClockOffset.java
new file mode 100644
index 0000000..fbb8132
--- /dev/null
+++ b/api/src/main/java/io/mifos/rhythm/api/v1/domain/ClockOffset.java
@@ -0,0 +1,101 @@
+/*
+ * 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.rhythm.api.v1.domain;
+
+import org.hibernate.validator.constraints.Range;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class ClockOffset {
+ @Range(min = 0, max = 23)
+ private Integer hours;
+
+ @Range(min = 0, max = 59)
+ private Integer minutes;
+
+ @Range(min = 0, max = 59)
+ private Integer seconds;
+
+ public ClockOffset() {
+ this.hours = 0;
+ this.minutes = 0;
+ this.seconds = 0;
+ }
+
+ public ClockOffset(Integer hours, Integer minutes) {
+ this.hours = hours;
+ this.minutes = minutes;
+ this.seconds = 0;
+ }
+
+ public ClockOffset(Integer hours, Integer minutes, Integer seconds) {
+ this.hours = hours;
+ this.minutes = minutes;
+ this.seconds = seconds;
+ }
+
+ public Integer getHours() {
+ return hours;
+ }
+
+ public void setHours(Integer hours) {
+ this.hours = hours;
+ }
+
+ public Integer getMinutes() {
+ return minutes;
+ }
+
+ public void setMinutes(Integer minutes) {
+ this.minutes = minutes;
+ }
+
+ public Integer getSeconds() {
+ return seconds;
+ }
+
+ public void setSeconds(Integer seconds) {
+ this.seconds = seconds;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ClockOffset that = (ClockOffset) o;
+ return Objects.equals(hours, that.hours) &&
+ Objects.equals(minutes, that.minutes) &&
+ Objects.equals(seconds, that.seconds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(hours, minutes, seconds);
+ }
+
+ @Override
+ public String toString() {
+ return "ClockOffset{" +
+ "hours=" + hours +
+ ", minutes=" + minutes +
+ ", seconds=" + seconds +
+ '}';
+ }
+}
diff --git a/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java b/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java
index 878999c..9f84439 100644
--- a/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java
+++ b/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java
@@ -27,8 +27,10 @@
String POST_BEAT = "post-beat";
String DELETE_APPLICATION = "delete-application";
String DELETE_BEAT = "delete-beat";
+ String PUT_CLOCKOFFSET = "put-clockoffset";
String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
String SELECTOR_POST_BEAT = SELECTOR_NAME + " = '" + POST_BEAT + "'";
String SELECTOR_DELETE_APPLICATION = SELECTOR_NAME + " = '" + DELETE_APPLICATION + "'";
String SELECTOR_DELETE_BEAT = SELECTOR_NAME + " = '" + DELETE_BEAT + "'";
+ String SELECTOR_PUT_CLOCKOFFSET = SELECTOR_NAME + " = '" + PUT_CLOCKOFFSET + "'";
}
diff --git a/api/src/test/java/io/mifos/rhythm/api/v1/domain/ClockOffsetTest.java b/api/src/test/java/io/mifos/rhythm/api/v1/domain/ClockOffsetTest.java
new file mode 100644
index 0000000..62bdc52
--- /dev/null
+++ b/api/src/test/java/io/mifos/rhythm/api/v1/domain/ClockOffsetTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.rhythm.api.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ClockOffsetTest extends ValidationTest<ClockOffset> {
+ public ClockOffsetTest(final ValidationTestCase<ClockOffset> testCase) {
+ super(testCase);
+ }
+
+ @Override
+ protected ClockOffset createValidTestSubject() {
+ return new ClockOffset();
+ }
+
+ @Parameterized.Parameters
+ public static Collection testCases() {
+ final Collection<ValidationTestCase> ret = new ArrayList<>();
+ ret.add(new ValidationTestCase<ClockOffset>("basicCase")
+ .adjustment(x -> {})
+ .valid(true));
+ ret.add(new ValidationTestCase<ClockOffset>("everythingMidRange")
+ .adjustment(x -> {x.setHours(12); x.setMinutes(30); x.setSeconds(30);})
+ .valid(true));
+ ret.add(new ValidationTestCase<ClockOffset>("negativeHours")
+ .adjustment(x -> x.setHours(-1))
+ .valid(false));
+ ret.add(new ValidationTestCase<ClockOffset>("outOfDayHours")
+ .adjustment(x -> x.setHours(24))
+ .valid(false));
+ ret.add(new ValidationTestCase<ClockOffset>("negativeMinutes")
+ .adjustment(x -> x.setMinutes(-1))
+ .valid(false));
+ ret.add(new ValidationTestCase<ClockOffset>("outOfRangeMinutes")
+ .adjustment(x -> x.setMinutes(60))
+ .valid(false));
+ ret.add(new ValidationTestCase<ClockOffset>("negativeSeconds")
+ .adjustment(x -> x.setMinutes(-1))
+ .valid(false));
+ ret.add(new ValidationTestCase<ClockOffset>("outOfRangeSeconds")
+ .adjustment(x -> x.setMinutes(60))
+ .valid(false));
+ return ret;
+ }
+}
\ No newline at end of file
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 7535700..b3890a9 100644
--- a/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
+++ b/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
@@ -17,6 +17,7 @@
import io.mifos.core.api.util.NotFoundException;
import io.mifos.rhythm.api.v1.domain.Beat;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
import io.mifos.rhythm.api.v1.events.BeatEvent;
import io.mifos.rhythm.api.v1.events.EventConstants;
import io.mifos.rhythm.service.internal.repository.BeatEntity;
@@ -28,6 +29,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import javax.transaction.Transactional;
+import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
@@ -131,6 +133,23 @@
}
@Test
+ public void shouldChangeTheTenantClockOffset() throws InterruptedException {
+ final ClockOffset initialClockOffset = this.testSubject.getClockOffset();
+ Assert.assertEquals(Integer.valueOf(0), initialClockOffset.getHours());
+ Assert.assertEquals(Integer.valueOf(0), initialClockOffset.getMinutes());
+ Assert.assertEquals(Integer.valueOf(0), initialClockOffset.getSeconds());
+
+ final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
+ final ClockOffset offsetToNow = new ClockOffset(now.getHour(), now.getMinute(), now.getSecond());
+ this.testSubject.setClockOffset(offsetToNow);
+
+ Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CLOCKOFFSET, offsetToNow));
+
+ final ClockOffset changedClockOffset = this.testSubject.getClockOffset();
+ Assert.assertEquals(offsetToNow, changedClockOffset);
+ }
+
+ @Test
public void shouldBeatForMissingDays() throws InterruptedException {
final String applicationIdentifier = "funnybusiness-v6";
final String beatIdentifier = "fiddlebeat";
diff --git a/component-test/src/main/java/io/mifos/rhythm/listener/ClockOffsetEventListener.java b/component-test/src/main/java/io/mifos/rhythm/listener/ClockOffsetEventListener.java
new file mode 100644
index 0000000..890df79
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/rhythm/listener/ClockOffsetEventListener.java
@@ -0,0 +1,51 @@
+/*
+ * 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.rhythm.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.api.v1.events.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Component
+public class ClockOffsetEventListener {
+ private final EventRecorder eventRecorder;
+
+ @Autowired
+ public ClockOffsetEventListener(@SuppressWarnings("SpringJavaAutowiringInspection") final EventRecorder eventRecorder) {
+ super();
+ this.eventRecorder = eventRecorder;
+ }
+
+ @JmsListener(
+ subscription = EventConstants.DESTINATION,
+ destination = EventConstants.DESTINATION,
+ selector = EventConstants.SELECTOR_PUT_CLOCKOFFSET
+ )
+ public void onChangeClockOffset(
+ @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+ final String payload) {
+ this.eventRecorder.event(tenant, EventConstants.PUT_CLOCKOFFSET, payload, ClockOffset.class);
+ }
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/ChangeClockOffsetCommand.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/ChangeClockOffsetCommand.java
new file mode 100644
index 0000000..36104bd
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/ChangeClockOffsetCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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.rhythm.service.internal.command;
+
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeClockOffsetCommand {
+ private final String tenantIdentifier;
+
+ private final ClockOffset instance;
+
+ public ChangeClockOffsetCommand(
+ final String tenantIdentifier,
+ final ClockOffset instance) {
+ this.tenantIdentifier = tenantIdentifier;
+ this.instance = instance;
+ }
+
+ public String getTenantIdentifier() {
+ return tenantIdentifier;
+ }
+
+ public ClockOffset getInstance() {
+ return instance;
+ }
+
+ @Override
+ public String toString() {
+ return "ChangeClockOffsetCommand{" +
+ "tenantIdentifier='" + tenantIdentifier + '\'' +
+ ", instance=" + instance +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ClockOffsetCommandHandler.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ClockOffsetCommandHandler.java
new file mode 100644
index 0000000..19d55d7
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ClockOffsetCommandHandler.java
@@ -0,0 +1,73 @@
+/*
+ * 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.rhythm.service.internal.command.handler;
+
+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.ServiceConstants;
+import io.mifos.rhythm.service.internal.command.ChangeClockOffsetCommand;
+import io.mifos.rhythm.service.internal.mapper.ClockOffsetMapper;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetEntity;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Aggregate
+public class ClockOffsetCommandHandler {
+ private final ClockOffsetRepository clockOffsetRepository;
+ private final EventHelper eventHelper;
+ private final Logger logger;
+
+ @Autowired
+ public ClockOffsetCommandHandler(
+ final ClockOffsetRepository clockOffsetRepository,
+ final EventHelper eventHelper,
+ @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
+ super();
+ this.clockOffsetRepository = clockOffsetRepository;
+ this.eventHelper = eventHelper;
+ this.logger = logger;
+ }
+
+ @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.NONE)
+ public void process(final ChangeClockOffsetCommand changeClockOffsetCommand) {
+
+ final Optional<ClockOffsetEntity> existingClockOffset =
+ clockOffsetRepository.findByTenantIdentifier(changeClockOffsetCommand.getTenantIdentifier());
+
+ final ClockOffsetEntity clockOffsetEntity = ClockOffsetMapper.map(
+ changeClockOffsetCommand.getTenantIdentifier(),
+ changeClockOffsetCommand.getInstance(),
+ existingClockOffset);
+
+ clockOffsetRepository.save(clockOffsetEntity);
+
+ logger.info("Sending change clock offset event.");
+ eventHelper.sendEvent(
+ EventConstants.PUT_CLOCKOFFSET,
+ changeClockOffsetCommand.getTenantIdentifier(),
+ changeClockOffsetCommand.getInstance());
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/mapper/ClockOffsetMapper.java b/service/src/main/java/io/mifos/rhythm/service/internal/mapper/ClockOffsetMapper.java
new file mode 100644
index 0000000..2188fda
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/mapper/ClockOffsetMapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.rhythm.service.internal.mapper;
+
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetEntity;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+public interface ClockOffsetMapper {
+ static ClockOffset map(final ClockOffsetEntity entity) {
+ final ClockOffset ret = new ClockOffset();
+ ret.setHours(entity.getHours());
+ ret.setMinutes(entity.getMinutes());
+ ret.setSeconds(entity.getSeconds());
+ return ret;
+ }
+
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+ static ClockOffsetEntity map(
+ final String tenantIdentifier,
+ final ClockOffset instance,
+ final Optional<ClockOffsetEntity> existingClockOffset) {
+ final ClockOffsetEntity ret = new ClockOffsetEntity();
+ existingClockOffset.ifPresent(x -> ret.setId(x.getId()));
+ ret.setTenantIdentifier(tenantIdentifier);
+ ret.setHours(instance.getHours());
+ ret.setMinutes(instance.getMinutes());
+ ret.setSeconds(instance.getSeconds());
+ return ret;
+ }
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetEntity.java b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetEntity.java
new file mode 100644
index 0000000..e105a95
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetEntity.java
@@ -0,0 +1,83 @@
+/*
+ * 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.rhythm.service.internal.repository;
+
+import javax.persistence.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+@Entity
+@Table(name = "khepri_clockoffsets")
+public class ClockOffsetEntity {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id")
+ private Long id;
+
+ @Column(name = "tenant_identifier", nullable = false)
+ private String tenantIdentifier;
+
+ @Column(name = "hours", nullable = false)
+ private Integer hours;
+
+ @Column(name = "minutes", nullable = false)
+ private Integer minutes;
+
+ @Column(name = "seconds", nullable = false)
+ private Integer seconds;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getTenantIdentifier() {
+ return tenantIdentifier;
+ }
+
+ public void setTenantIdentifier(String tenantIdentifier) {
+ this.tenantIdentifier = tenantIdentifier;
+ }
+
+ public Integer getHours() {
+ return hours;
+ }
+
+ public void setHours(Integer hours) {
+ this.hours = hours;
+ }
+
+ public Integer getMinutes() {
+ return minutes;
+ }
+
+ public void setMinutes(Integer minutes) {
+ this.minutes = minutes;
+ }
+
+ public Integer getSeconds() {
+ return seconds;
+ }
+
+ public void setSeconds(Integer seconds) {
+ this.seconds = seconds;
+ }
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetRepository.java b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetRepository.java
new file mode 100644
index 0000000..3005457
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetRepository.java
@@ -0,0 +1,24 @@
+/*
+ * 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.rhythm.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface ClockOffsetRepository extends JpaRepository<ClockOffsetEntity, Long> {
+ Optional<ClockOffsetEntity> findByTenantIdentifier(String tenantIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/ClockOffsetService.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/ClockOffsetService.java
new file mode 100644
index 0000000..07c17e0
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/ClockOffsetService.java
@@ -0,0 +1,41 @@
+/*
+ * 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.rhythm.service.internal.service;
+
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.service.internal.mapper.ClockOffsetMapper;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ClockOffsetService {
+ final private ClockOffsetRepository clockOffsetRepository;
+
+ @Autowired
+ public ClockOffsetService(final ClockOffsetRepository clockOffsetRepository) {
+ this.clockOffsetRepository = clockOffsetRepository;
+ }
+
+ public ClockOffset findByTenant(final String tenantIdentifier) {
+ return clockOffsetRepository.findByTenantIdentifier(tenantIdentifier)
+ .map(ClockOffsetMapper::map)
+ .orElseGet(ClockOffset::new);
+ }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/rest/ClockOffsetRestController.java b/service/src/main/java/io/mifos/rhythm/service/rest/ClockOffsetRestController.java
new file mode 100644
index 0000000..a788d28
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/rest/ClockOffsetRestController.java
@@ -0,0 +1,77 @@
+/*
+ * 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.rhythm.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.service.internal.command.ChangeClockOffsetCommand;
+import io.mifos.rhythm.service.internal.service.ClockOffsetService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+import static io.mifos.core.lang.config.TenantHeaderFilter.TENANT_HEADER;
+
+/**
+ * @author Myrle Krantz
+ */
+@RestController
+@RequestMapping("/clockoffset")
+public class ClockOffsetRestController {
+ private final CommandGateway commandGateway;
+ private final ClockOffsetService clockOffsetService;
+
+ @Autowired
+ public ClockOffsetRestController(
+ final CommandGateway commandGateway,
+ final ClockOffsetService clockOffsetService) {
+ super();
+ this.commandGateway = commandGateway;
+ this.clockOffsetService = clockOffsetService;
+ }
+
+ @Permittable(value = AcceptedTokenType.SYSTEM)
+ @RequestMapping(
+ method = RequestMethod.GET,
+ consumes = MediaType.ALL_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<ClockOffset> getClockOffset(@RequestHeader(TENANT_HEADER) final String tenantIdentifier) {
+ return ResponseEntity.ok(this.clockOffsetService.findByTenant(tenantIdentifier));
+ }
+
+ @Permittable(value = AcceptedTokenType.SYSTEM)
+ @RequestMapping(
+ method = RequestMethod.PUT,
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public
+ @ResponseBody
+ ResponseEntity<Void> setClockOffset(
+ @RequestHeader(TENANT_HEADER) final String tenantIdentifier,
+ @RequestBody @Valid final ClockOffset instance) throws InterruptedException {
+ this.commandGateway.process(new ChangeClockOffsetCommand(tenantIdentifier, instance));
+ return ResponseEntity.accepted().build();
+ }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V2__tenant_clock_offset.sql b/service/src/main/resources/db/migrations/mariadb/V2__tenant_clock_offset.sql
new file mode 100644
index 0000000..c6b3662
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V2__tenant_clock_offset.sql
@@ -0,0 +1,25 @@
+--
+-- 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.
+--
+
+CREATE TABLE khepri_clockoffsets (
+ id BIGINT NOT NULL AUTO_INCREMENT,
+ tenant_identifier VARCHAR(32) NOT NULL,
+ hours INT NOT NULL,
+ minutes INT NOT NULL,
+ seconds INT NOT NULL,
+ CONSTRAINT khepri_clockoffsets_uq UNIQUE (tenant_identifier),
+ CONSTRAINT khepri_clockoffsets_pk PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/shared.gradle b/shared.gradle
index dc7dd71..0170643 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -65,7 +65,9 @@
xml = 'XML_STYLE'
yml = 'SCRIPT_STYLE'
yaml = 'SCRIPT_STYLE'
+ uxf = 'XML_STYLE'
}
ext.year = Calendar.getInstance().get(Calendar.YEAR)
- ext.name = 'The Mifos Initiative'
+ ext.name = 'Kuelap, Inc'
+ skipExistingHeaders true
}