Skip to content

Commit

Permalink
Merge pull request #466 from Health-Education-England/feat/localOffic…
Browse files Browse the repository at this point in the history
…eSync

feat: include LocalOffice in sync db
  • Loading branch information
ReubenRobertsHEE authored Jul 12, 2024
2 parents f33b598 + 78ec852 commit da9803a
Show file tree
Hide file tree
Showing 10 changed files with 827 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = "uk.nhs.hee.tis.trainee"
version = "1.14.0"
version = "1.15.0"

configurations {
compileOnly {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.data.mongodb.core.index.Index;
import org.springframework.data.mongodb.core.index.IndexOperations;
import uk.nhs.hee.tis.trainee.sync.model.CurriculumMembership;
import uk.nhs.hee.tis.trainee.sync.model.LocalOffice;
import uk.nhs.hee.tis.trainee.sync.model.Placement;
import uk.nhs.hee.tis.trainee.sync.model.PlacementSite;
import uk.nhs.hee.tis.trainee.sync.model.PlacementSpecialty;
Expand Down Expand Up @@ -68,6 +69,10 @@ public void initIndexes() {
.named("curriculumMembershipCompoundIndex");
cmIndexOps.ensureIndex(curriculumMembershipCompoundIndex);

// LocalOffice
IndexOperations localOfficeIndexOps = template.indexOps(LocalOffice.class);
localOfficeIndexOps.ensureIndex(new Index().on("data.abbreviation", Direction.ASC));

// Placement
IndexOperations placementIndexOps = template.indexOps(Placement.class);
placementIndexOps.ensureIndex(new Index().on("data.postId", Direction.ASC));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* The MIT License (MIT)
*
* Copyright 2024 Crown Copyright (Health Education England)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package uk.nhs.hee.tis.trainee.sync.event;

import java.util.Optional;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
import org.springframework.data.mongodb.core.mapping.event.BeforeDeleteEvent;
import org.springframework.stereotype.Component;
import uk.nhs.hee.tis.trainee.sync.model.LocalOffice;
import uk.nhs.hee.tis.trainee.sync.model.Operation;
import uk.nhs.hee.tis.trainee.sync.model.Programme;
import uk.nhs.hee.tis.trainee.sync.service.FifoMessagingService;
import uk.nhs.hee.tis.trainee.sync.service.LocalOfficeSyncService;
import uk.nhs.hee.tis.trainee.sync.service.ProgrammeSyncService;

/**
* A listener for Mongo events associated with Local office data.
*/
@Component
@Slf4j
public class LocalOfficeEventListener extends AbstractMongoEventListener<LocalOffice> {

static final String LOCAL_OFFICE_NAME = "name";

private final LocalOfficeSyncService localOfficeSyncService;

private final ProgrammeSyncService programmeSyncService;

private final FifoMessagingService fifoMessagingService;

private final String programmeQueueUrl;

private final Cache cache;

LocalOfficeEventListener(LocalOfficeSyncService localOfficeSyncService,
ProgrammeSyncService programmeService,
FifoMessagingService fifoMessagingService,
@Value("${application.aws.sqs.programme}") String programmeQueueUrl,
CacheManager cacheManager) {
this.localOfficeSyncService = localOfficeSyncService;
this.programmeSyncService = programmeService;
this.fifoMessagingService = fifoMessagingService;
this.programmeQueueUrl = programmeQueueUrl;
cache = cacheManager.getCache(LocalOffice.ENTITY_NAME);
}

@Override
public void onAfterSave(AfterSaveEvent<LocalOffice> event) {
super.onAfterSave(event);

LocalOffice localOffice = event.getSource();
cache.put(localOffice.getTisId(), localOffice);

queueRelatedProgrammes(localOffice);
}

/**
* Before deleting a LocalOffice, ensure it is cached.
*
* @param event The before-delete event for the LocalOffice.
*/
@Override
public void onBeforeDelete(BeforeDeleteEvent<LocalOffice> event) {
String id = event.getSource().getString("_id");
LocalOffice localOffice = cache.get(id, LocalOffice.class);
if (localOffice == null) {
Optional<LocalOffice> newLocalOffice = localOfficeSyncService.findById(id);
newLocalOffice.ifPresent(loPresent ->
cache.put(id, loPresent));
}
}

/**
* After delete retrieve cached values and re-sync related Programmes.
*
* @param event The after-delete event for the DBC.
*/
@Override
public void onAfterDelete(AfterDeleteEvent<LocalOffice> event) {
super.onAfterDelete(event);
LocalOffice localOffice = cache.get(event.getSource().getString("_id"), LocalOffice.class);
if (localOffice != null) {
queueRelatedProgrammes(localOffice);
}
}

/**
* Queue the programmes related to the given LocalOffice.
*
* @param localOffice The LocalOffice to get related programmes for.
*/
private void queueRelatedProgrammes(LocalOffice localOffice) {
//If the LO abbreviation changes then that could mean it links to a different DBC
//so then the RO could change. This seems quite unlikely but needs to be handled.
Set<Programme> programmes =
programmeSyncService.findByOwner(localOffice.getData().get(LOCAL_OFFICE_NAME));

for (Programme programme : programmes) {
log.debug("LocalOffice {} affects programme {}, "
+ "and may require related programme memberships to have RO data amended.",
localOffice.getData().get(LOCAL_OFFICE_NAME), programme.getTisId());
// Default each message to LOAD.
programme.setOperation(Operation.LOAD);
String deduplicationId = fifoMessagingService
.getUniqueDeduplicationId(Programme.ENTITY_NAME, programme.getTisId());
fifoMessagingService.sendMessageToFifoQueue(programmeQueueUrl, programme, deduplicationId);
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/uk/nhs/hee/tis/trainee/sync/model/LocalOffice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* The MIT License (MIT)
*
* Copyright 2024 Crown Copyright (Health Education England)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package uk.nhs.hee.tis.trainee.sync.model;

import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
* A class for TIS reference Local Office entities.
*/
@Component(LocalOffice.ENTITY_NAME)
@Scope(SCOPE_PROTOTYPE)
public class LocalOffice extends Record {

public static final String ENTITY_NAME = "LocalOffice";
public static final String SCHEMA_NAME = "reference";

/**
* Instantiate with correct default table and schema values.
*/
public LocalOffice() {
super();
setSchema(SCHEMA_NAME);
setTable(ENTITY_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* The MIT License (MIT)
*
* Copyright 2024 Crown Copyright (Health Education England)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package uk.nhs.hee.tis.trainee.sync.repository;

import java.util.Optional;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;
import uk.nhs.hee.tis.trainee.sync.model.LocalOffice;

/**
* A repository for LocalOffice entities.
*/
@CacheConfig(cacheNames = LocalOffice.ENTITY_NAME)
@Repository
public interface LocalOfficeRepository extends MongoRepository<LocalOffice, String> {

@Cacheable
@Override
Optional<LocalOffice> findById(String id);

@Query("{ 'data.abbreviation' : ?0}")
Optional<LocalOffice> findByAbbreviation(String abbreviation);

@CachePut(key = "#entity.tisId")
@Override
<T extends LocalOffice> T save(T entity);

@CacheEvict
@Override
void deleteById(String id);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* The MIT License (MIT)
*
* Copyright 2024 Crown Copyright (Health Education England)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package uk.nhs.hee.tis.trainee.sync.service;

import static uk.nhs.hee.tis.trainee.sync.model.Operation.DELETE;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import uk.nhs.hee.tis.trainee.sync.model.LocalOffice;
import uk.nhs.hee.tis.trainee.sync.model.Record;
import uk.nhs.hee.tis.trainee.sync.repository.LocalOfficeRepository;

/**
* A service for managing Local Office synchronisation.
*/
@Slf4j
@Service("reference-LocalOffice")
public class LocalOfficeSyncService implements SyncService {

private final LocalOfficeRepository repository;

private final DataRequestService dataRequestService;

private final ReferenceSyncService referenceSyncService;

private final RequestCacheService requestCacheService;

LocalOfficeSyncService(LocalOfficeRepository repository, DataRequestService dataRequestService,
ReferenceSyncService referenceSyncService, RequestCacheService requestCacheService) {
this.repository = repository;
this.dataRequestService = dataRequestService;
this.referenceSyncService = referenceSyncService;
this.requestCacheService = requestCacheService;
}

@Override
public void syncRecord(Record localOffice) {
if (!(localOffice instanceof LocalOffice)) {
String message = String.format("Invalid record type '%s'.", localOffice.getClass());
throw new IllegalArgumentException(message);
}

if (localOffice.getOperation().equals(DELETE)) {
repository.deleteById(localOffice.getTisId());
} else {
repository.save((LocalOffice) localOffice);
}

requestCacheService.deleteItemFromCache(LocalOffice.ENTITY_NAME, localOffice.getTisId());

// Send the record to the reference sync service to also be handled as a reference data type.
referenceSyncService.syncRecord(localOffice);
}

public Optional<LocalOffice> findById(String id) {
return repository.findById(id);
}

public Optional<LocalOffice> findByAbbreviation(String abbr) {
return repository.findByAbbreviation(abbr);
}

/**
* Make a request to retrieve a specific LocalOffice.
*
* @param id The id of the LocalOffice to be retrieved.
*/
public void request(String id) {
if (!requestCacheService.isItemInCache(LocalOffice.ENTITY_NAME, id)) {
log.info("Sending request for LocalOffice [{}]", id);

try {
requestCacheService.addItemToCache(LocalOffice.ENTITY_NAME, id,
dataRequestService.sendRequest("reference", LocalOffice.ENTITY_NAME, Map.of("id", id)));
} catch (JsonProcessingException e) {
log.error("Error while trying to retrieve a LocalOffice", e);
}
} else {
log.debug("Already requested LocalOffice [{}].", id);
}
}
}
Loading

0 comments on commit da9803a

Please sign in to comment.