Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 41 additions & 31 deletions src/main/java/org/fairdatapoint/config/BootstrapConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@

import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

/**
* The {@code BootstrapConfig} class configures a repository populator that loads initial data into the relational
Expand All @@ -61,49 +62,58 @@ public class BootstrapConfig {
private final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private final BootstrapProperties bootstrap;
private final FixtureHistoryRepository fixtureHistoryRepository;
private final List<Resource> resources = new ArrayList<>();

public BootstrapConfig(BootstrapProperties bootstrapProperties, FixtureHistoryRepository fixtureHistoryRepository) {
this.bootstrap = bootstrapProperties;
this.fixtureHistoryRepository = fixtureHistoryRepository;
}

@Bean
public Jackson2RepositoryPopulatorFactoryBean repositoryPopulator() {
final Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
if (this.bootstrap.isEnabled()) {
log.info("Bootstrap repository populator enabled");
/**
* Creates a sorted array of unique fixture resources, representing files in the specified fixtures directories.
* Checks the fixture history repository for files that have already been applied, and removes them from the array.
* @return sorted array of unique Resource objects
*/
public Resource[] getNewResources() {
// use TreeSet with comparator for lexicographic order and uniqueness
final SortedSet<Resource> resources = new TreeSet<>(
Comparator.comparing(Resource::getFilename, Comparator.nullsLast(String::compareTo)));
// collect fixture resources from specified directories
log.info("Looking for db fixtures in the following directories: {}",
String.join(", ", this.bootstrap.getDbFixturesDirs()));
for (String fixturesDir : this.bootstrap.getDbFixturesDirs()) {
// Path.of() removes trailing slashes, so it is safe to concatenate "/*.json".
// Note that Path.of(fixturesDir).resolve("*.json") could work on unix but fails on windows.
final String locationPattern = "file:" + Path.of(fixturesDir) + "/*.json";
try {
// collect fixture resources
log.info("Looking for db fixtures in the following directories: {}",
String.join(", ", this.bootstrap.getDbFixturesDirs()));
for (String fixturesDir : this.bootstrap.getDbFixturesDirs()) {
// Path.of() removes trailing slashes, so it is safe to concatenate "/*.json".
// Note that Path.of(fixturesDir).resolve("*.json") could work on unix but fails on windows.
final String locationPattern = "file:" + Path.of(fixturesDir) + "/*.json";
resources.addAll(List.of(resourceResolver.getResources(locationPattern)));
}
// remove resources that have been applied already
final List<String> appliedFixtures = fixtureHistoryRepository.findAll().stream()
.map(FixtureHistory::getFilename).toList();
final List<Resource> resourcesToSkip = resources.stream()
.filter(resource -> appliedFixtures.contains(resource.getFilename())).toList();
resources.removeAll(resourcesToSkip);
// sort resources to guarantee lexicographic order
resources.sort(Comparator.comparing(Resource::getFilename, Comparator.nullsLast(String::compareTo)));
// add resources to factory
log.info("Applying {} db fixtures ({} have been applied already)",
resources.size(), resourcesToSkip.size());
factory.setResources(resources.toArray(new Resource[0]));
resources.addAll(List.of(resourceResolver.getResources(locationPattern)));
}
catch (IOException exception) {
log.error("Failed to load relational database fixtures", exception);
log.error("Failed to resolve fixture resources", exception);
}
}
// remove resources that have been applied already
final List<String> appliedFixtures = fixtureHistoryRepository.findAll().stream()
.map(FixtureHistory::getFilename).toList();
final List<Resource> resourcesToSkip = resources.stream()
.filter(resource -> appliedFixtures.contains(resource.getFilename())).toList();
resourcesToSkip.forEach(resources::remove);
// return the result
log.info("Found {} new db fixture files ({} have been applied already)",
resources.size(), resourcesToSkip.size());
return resources.toArray(new Resource[0]);
}

@Bean
public Jackson2RepositoryPopulatorFactoryBean repositoryPopulatorFactoryBean() {
final Jackson2RepositoryPopulatorFactoryBean factory = new Jackson2RepositoryPopulatorFactoryBean();
if (this.bootstrap.isEnabled()) {
log.info("Bootstrap repository populator enabled");
// add resources to factory
factory.setResources(getNewResources());
}
else {
log.info("Bootstrap repository populator disabled");
}

return factory;
}

Expand All @@ -116,7 +126,7 @@ public void onApplicationEvent(@NotNull RepositoriesPopulatedEvent event) {
// Note: This assumes that all items in the resources list have been *successfully* applied. However, I'm
// not sure if this can be guaranteed. If it does turn out to be a problem, we could try e.g. extending the
// ResourceReaderRepositoryPopulator.persist() method, so the history record is added there.
for (final Resource resource : resources) {
for (final Resource resource : getNewResources()) {
final String filename = resource.getFilename();
final FixtureHistory fixtureHistory = fixtureHistoryRepository.save(new FixtureHistory(filename));
log.debug("Fixture history updated: {} ({})", fixtureHistory.getFilename(), fixtureHistory.getUuid());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@
import org.fairdatapoint.database.db.repository.base.BaseRepository;
import org.fairdatapoint.entity.bootstrap.FixtureHistory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Repository
public interface FixtureHistoryRepository extends BaseRepository<FixtureHistory> {
Optional<FixtureHistory> findByFilename(String filename);

@Transactional
void deleteByFilenameIn(String[] filenames);
}
93 changes: 65 additions & 28 deletions src/main/java/org/fairdatapoint/service/reset/ResetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import lombok.extern.slf4j.Slf4j;
import org.fairdatapoint.api.dto.reset.ResetDTO;
import org.fairdatapoint.config.BootstrapConfig;
import org.fairdatapoint.database.db.repository.*;
import org.fairdatapoint.entity.resource.ResourceDefinition;
import org.fairdatapoint.service.metadata.exception.MetadataServiceException;
Expand All @@ -39,6 +40,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.data.repository.init.ResourceReaderRepositoryPopulator;
import org.springframework.data.repository.support.Repositories;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.acls.model.AclCache;
import org.springframework.stereotype.Service;
Expand All @@ -52,6 +56,21 @@
@Service
public class ResetService {

@Autowired
private ApplicationContext applicationContext;

@Autowired
private BootstrapConfig bootstrapConfig;

@Autowired
private FixtureHistoryRepository fixtureHistoryRepository;

@Autowired
private MembershipPermissionRepository membershipPermissionRepository;

@Autowired
private ResourceReaderRepositoryPopulator populator;

@Autowired
@Qualifier("persistentUrl")
private String persistentUrl;
Expand Down Expand Up @@ -86,9 +105,21 @@ public class ResetService {
@Autowired
private MetadataSchemaRepository metadataSchemaRepository;

@Autowired
private MetadataSchemaUsageRepository metadataSchemaUsageRepository;

@Autowired
private ResourceDefinitionRepository resourceDefinitionRepository;

@Autowired
private ResourceDefinitionChildRepository resourceDefinitionChildRepository;

@Autowired
private ResourceDefinitionChildMetadataRepository resourceDefinitionChildMetadataRepository;

@Autowired
private ResourceDefinitionLinkRepository resourceDefinitionLinkRepository;

@Autowired
private ResourceDefinitionCache resourceDefinitionCache;

Expand All @@ -109,52 +140,66 @@ public void resetToFactoryDefaults(ResetDTO reqDto) throws Exception {
}
if (reqDto.isUsers() || reqDto.isMetadata()) {
clearMemberships();
restoreDefaultMemberships();
}
if (reqDto.isUsers()) {
clearApiKeys();
clearUsers();
restoreDefaultUsers();
}
if (reqDto.isMetadata()) {
clearMetadata();
restoreDefaultMetadata();
}
if (reqDto.isResourceDefinitions()) {
clearResourceDefinitions();
clearMetadataSchemas();
restoreDefaultMetadataSchemas();
restoreDefaultResourceDefinitions();
clearMetadataSchemasAndResourceDefinitions();
}
// restoreDefaultFixtures is safe to call even if there are no changes
restoreDefaultFixtures();
resourceDefinitionCache.computeCache();
resourceDefinitionTargetClassesCache.computeCache();
}

private void clearApiKeys() {
log.debug("Clearing API keys");
apiKeyRepository.deleteAll();
removeFromFixtureHistory(new String[]{"0110_api-keys.json"});
}

private void clearMemberships() {
log.debug("Clearing membership permissions");
membershipPermissionRepository.deleteAll();
log.debug("Clearing memberships");
membershipRepository.deleteAll();
log.debug("Clearing ACL cache");
aclCache.clearCache();
removeFromFixtureHistory(new String[]{"0400_memberships_owner.json", "0410_memberships_data-provider.json"});
}

private void clearUsers() {
log.debug("Clearing users");
userRepository.deleteAll();
removeFromFixtureHistory(new String[]{"0100_user-accounts.json", "0120_saved-queries.json"});
}

private void clearMetadataSchemas() {
log.debug("Clearing metadata schemas");
private void clearMetadataSchemasAndResourceDefinitions() {
log.debug("Clearing metadata schemas and resource definitions");
metadataSchemaUsageRepository.deleteAll();
metadataSchemaRepository.deleteAll();
}

private void clearResourceDefinitions() {
log.debug("Clearing resource definitions");
resourceDefinitionChildMetadataRepository.deleteAll();
resourceDefinitionChildRepository.deleteAll();
resourceDefinitionLinkRepository.deleteAll();
resourceDefinitionRepository.deleteAll();
removeFromFixtureHistory(new String[]{
"0200_metadata-schemas_resource.json",
"0210_metadata-schemas_data-service.json",
"0220_metadata-schemas_metadata-service.json",
"0230_metadata-schemas_fdp.json",
"0240_metadata-schemas_catalog.json",
"0250_metadata-schemas_dataset.json",
"0260_metadata-schemas_distribution.json",
"0300_resource-definitions_distribution.json",
"0310_resource-definitions_dataset.json",
"0320_resource-definitions_catalog.json",
"0330_resource-definitions_repository.json"});
}

private void clearMetadata() throws MetadataServiceException {
Expand All @@ -166,14 +211,16 @@ private void clearMetadata() throws MetadataServiceException {
}
}

private void restoreDefaultUsers() {
log.debug("Creating default users");
// TODO: data seed from specs
protected void removeFromFixtureHistory(String[] filenames) {
log.debug("Removing filenames from fixture history: {}", String.join(", ", filenames));
fixtureHistoryRepository.deleteByFilenameIn(filenames);
}

private void restoreDefaultMemberships() {
log.debug("Creating default memberships");
// TODO: data seed from specs
protected void restoreDefaultFixtures() {
log.debug("Restoring default fixtures");
// getNewResources() checks the updated fixture history
populator.setResources(bootstrapConfig.getNewResources());
populator.populate(new Repositories(applicationContext));
}

private void restoreDefaultMetadata() {
Expand All @@ -191,14 +238,4 @@ private void restoreDefaultMetadata() {
log.error(exception.getMessage(), exception);
}
}

private void restoreDefaultMetadataSchemas() throws Exception {
log.debug("Creating default metadata schemas");
// TODO: data seed from specs
}

private void restoreDefaultResourceDefinitions() {
log.debug("Creating default resource definitions");
// TODO: data seed from specs
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,28 @@
import org.fairdatapoint.BaseIntegrationTest;
import org.fairdatapoint.database.db.repository.FixtureHistoryRepository;
import org.fairdatapoint.entity.bootstrap.FixtureHistory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestEntityManager;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.test.context.TestPropertySource;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;


@AutoConfigureTestEntityManager
@Transactional
@TestPropertySource(properties = "bootstrap.enabled=false")
public class FixtureHistoryRepositoryTests extends BaseIntegrationTest {
@Autowired
FixtureHistoryRepository repository;

final String filename = "0001-whatever.json";

@BeforeEach
public void clearFixtureHistory() {
repository.deleteAll();
}

@Test
public void testSave() {
FixtureHistory fixtureHistory = repository.saveAndFlush(new FixtureHistory(filename));
Expand Down Expand Up @@ -79,4 +81,14 @@ public void testFindByFilenameWithExistingFilename() {
Optional<FixtureHistory> appliedFixture = repository.findByFilename(filename);
assertTrue(appliedFixture.isPresent());
}

@Test
public void testDeleteByFilenameIn() {
// prepare
repository.saveAndFlush(new FixtureHistory(filename));
assertEquals(1, repository.count());
// test
repository.deleteByFilenameIn(new String[] {filename});
assertEquals(0, repository.count());
}
}