diff --git a/Jenkinsfile b/Jenkinsfile index e4019fea96..ecb912d3e7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -98,6 +98,12 @@ pipeline { Unit Tests */ stage('Unit Tests') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew --build-cache test" } @@ -112,6 +118,12 @@ pipeline { Integration Tests */ stage('Integration Tests') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh './gradlew --build-cache -Dgradle.integrationTest=true ' + [ 'mdm-core', @@ -138,6 +150,12 @@ pipeline { Functional Tests */ stage('Functional Test: mdm-core') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-core:integrationTest" } @@ -148,6 +166,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-authentication-apikey') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-authentication-apikey:integrationTest" } @@ -158,6 +182,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-authentication-basic') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-authentication-basic:integrationTest" } @@ -168,6 +198,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-dataflow') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-dataflow:integrationTest" } @@ -178,6 +214,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-datamodel') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-datamodel:integrationTest" } @@ -188,6 +230,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-referencedata') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-referencedata:integrationTest" } @@ -198,6 +246,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-terminology') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-terminology:integrationTest" } @@ -208,6 +262,12 @@ pipeline { } } stage('Functional Test: mdm-security') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-security:integrationTest" } @@ -218,6 +278,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-profile') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-profile:integrationTest" } @@ -228,6 +294,12 @@ pipeline { } } stage('Functional Test: mdm-plugin-federation') { + // Dont run these on main branch + when { + not { + branch 'main' + } + } steps { sh "./gradlew -Dgradle.functionalTest=true :mdm-plugin-federation:integrationTest" } @@ -321,16 +393,16 @@ pipeline { } } } -// stage('E2E Profile Functional Test') { -// steps { -// sh "./gradlew -Dgradle.test.package=profile :mdm-testing-functional:integrationTest" -// } -// post { -// always { -// junit allowEmptyResults: true, testResults: 'mdm-testing-functional/build/test-results/profile/*.xml' -// } -// } -// } + // stage('E2E Profile Functional Test') { + // steps { + // sh "./gradlew -Dgradle.test.package=profile :mdm-testing-functional:integrationTest" + // } + // post { + // always { + // junit allowEmptyResults: true, testResults: 'mdm-testing-functional/build/test-results/profile/*.xml' + // } + // } + // } stage('Compile complete Test Report') { steps { @@ -359,7 +431,7 @@ pipeline { stage('Sonarqube') { when { - branch 'develop' + branch 'develop' } steps { withSonarQubeEnv('JenkinsQube') { @@ -395,7 +467,8 @@ pipeline { publishCoverage adapters: [jacocoAdapter('**/reports/jacoco/jacocoTestReport.xml')] outputTestResults() - jacoco classPattern: '**/build/classes', execPattern: '**/build/jacoco/*.exec', sourceInclusionPattern: '**/*.java,**/*.groovy', sourcePattern: '**/src/main/groovy,**/grails-app/controllers,**/grails-app/domain,**/grails-app/services,**/grails-app/utils' + jacoco classPattern: '**/build/classes', execPattern: '**/build/jacoco/*.exec', sourceInclusionPattern: '**/*.java,**/*.groovy', + sourcePattern: '**/src/main/groovy,**/grails-app/controllers,**/grails-app/domain,**/grails-app/services,**/grails-app/utils' archiveArtifacts allowEmptyArchive: true, artifacts: '**/*.log' slackNotification() zulipNotification(topic: 'mdm-core') diff --git a/build.gradle b/build.gradle index 0436433819..b12e881052 100644 --- a/build.gradle +++ b/build.gradle @@ -7,8 +7,8 @@ import java.util.concurrent.TimeUnit buildscript { repositories { mavenLocal() - maven { url 'https://jenkins.cs.ox.ac.uk/artifactory/plugins-snapshot' } - maven { url 'https://jenkins.cs.ox.ac.uk/artifactory/plugins-release' } + maven {url 'https://jenkins.cs.ox.ac.uk/artifactory/plugins-snapshot'} + maven {url 'https://jenkins.cs.ox.ac.uk/artifactory/plugins-release'} jcenter() } @@ -70,7 +70,7 @@ check { task('sysProps') { group 'help' doLast { - logger.quiet('{}', System.properties.collect { "${it.key}:${it.value}" }.sort().join('\n')) + logger.quiet('{}', System.properties.collect {"${it.key}:${it.value}"}.sort().join('\n')) } } @@ -113,10 +113,10 @@ task rootTestReport(type: TestReport) { destinationDir = file("${buildDir}/reports/tests") testResultDirs = files("${buildDir}/test-results") FileCollection testResultContentDir = files("${buildDir}/test-results") - outputs.upToDateWhen { false } + outputs.upToDateWhen {false} doFirst { - (testResultContentDir.getAsFileTree().visit { FileVisitDetails details -> + (testResultContentDir.getAsFileTree().visit {FileVisitDetails details -> if (details.directory && details.name == 'binary') { logger.info("Reporting on ${details.path}") reportOn files(details.file) @@ -169,7 +169,7 @@ subprojects { doLast { if (project.hasProperty('dependencyManagement')) { Map imported = dependencyManagement.importedProperties - logger.quiet 'Project :: {}\n {}', project.name, imported.collect { k, v -> "$k:$v" }.sort().join('\n ') + logger.quiet 'Project :: {}\n {}', project.name, imported.collect {k, v -> "$k:$v"}.sort().join('\n ') } } } @@ -184,14 +184,37 @@ subprojects { } afterEvaluate { - project.tasks.withType(Test) { testTask -> + project.tasks.withType(Test) {testTask -> ignoreFailures = System.getenv().containsKey('JENKINS') } -// Set dependencyProjects = collectProjectDependencies(project) -// if (dependencyProjects) { -// project.tasks.findByName('install').dependsOn dependencyProjects.collect { it.tasks.findByName('install') } -// } + // Set dependencyProjects = collectProjectDependencies(project) + // if (dependencyProjects) { + // project.tasks.findByName('install').dependsOn dependencyProjects.collect { it.tasks.findByName('install') } + // } + } +} + +afterEvaluate { + /* + Massive hack to solve parallel task running for assetCompile task + Make sure that each task mustRunAfter another assetCompile task, this ensures none of them can run at the same time + We have to allow for project dependencies so make sure thats accounted for manually + */ + List assetCompileTasks = it.getTasksByName('assetCompile', true).toList().sort{it.path} + + Task coreTask = assetCompileTasks.find {it.path.startsWith(':mdm-core')} + Task dataModelTask = assetCompileTasks.find {it.path.startsWith(':mdm-plugin-datamodel')} + + assetCompileTasks.remove(coreTask) + assetCompileTasks.remove(dataModelTask) + dataModelTask.mustRunAfter coreTask + assetCompileTasks.each{ + it.mustRunAfter coreTask, dataModelTask + } + + for (int i = 1; i < assetCompileTasks.size(); i++) { + assetCompileTasks[i].mustRunAfter assetCompileTasks[i-1] } } diff --git a/gradle.properties b/gradle.properties index 7abd1e6ddb..542b80656f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Core Info -version=4.6.0 +version=4.7.0 group=uk.ac.ox.softeng.maurodatamapper # Gradle gradleVersion=6.7.1 @@ -25,7 +25,7 @@ luceneVersion=5.5.5 commonsTextVersion=1.9 springBootVersion=2.1.17.RELEASE jaxbApiVersion=2.3.1 -assetPipelineVersion=3.2.4 +assetPipelineVersion=3.3.2 guavaVersion=28.1-jre antVersion=1.9.13 javaMailVersion=5.4.0 diff --git a/mdm-core/grails-app/conf/application.yml b/mdm-core/grails-app/conf/application.yml index eb56c964ea..7837b4e1d5 100644 --- a/mdm-core/grails-app/conf/application.yml +++ b/mdm-core/grails-app/conf/application.yml @@ -168,6 +168,10 @@ dataSource: --- environments: test: + maurodatamapper: + authority: + name: 'Test Authority' + url: 'http://localhost' spring.flyway.enabled: false database: name: 'mdmTest' diff --git a/mdm-core/grails-app/conf/db/migration/core/beforeMigrate.sql b/mdm-core/grails-app/conf/db/migration/core/beforeMigrate.sql new file mode 100644 index 0000000000..491a8896d0 --- /dev/null +++ b/mdm-core/grails-app/conf/db/migration/core/beforeMigrate.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA public; diff --git a/mdm-core/grails-app/conf/db/migration/core/beforeValidate.sql b/mdm-core/grails-app/conf/db/migration/core/beforeValidate.sql index ebfc70c668..3b49b19daf 100644 --- a/mdm-core/grails-app/conf/db/migration/core/beforeValidate.sql +++ b/mdm-core/grails-app/conf/db/migration/core/beforeValidate.sql @@ -1,4 +1,9 @@ UPDATE core.flyway_schema_history SET checksum = 490568862 WHERE version = '1.7.0' AND - checksum = -1413608684 \ No newline at end of file + checksum = -1413608684; + +DELETE +FROM core.flyway_schema_history +WHERE version = '2.10.0' AND + description = 'update database metadata values'; \ No newline at end of file diff --git a/mdm-core/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/core/authority/Authority.groovy b/mdm-core/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/core/authority/Authority.groovy index a659c0c4b2..683a998343 100644 --- a/mdm-core/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/core/authority/Authority.groovy +++ b/mdm-core/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/core/authority/Authority.groovy @@ -54,4 +54,10 @@ class Authority implements InformationAware, CreatorAware, SecurableResource { String getDomainType() { Authority.simpleName } + + + @Override + String toString() { + "${label}@${url}" + } } diff --git a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/ClassifierService.groovy b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/ClassifierService.groovy index 8ac437e595..d91206e368 100644 --- a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/ClassifierService.groovy +++ b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/ClassifierService.groovy @@ -66,7 +66,7 @@ class ClassifierService extends ContainerService { @Override List getAll(Collection containerIds) { - Classifier.getAll(containerIds) + Classifier.getAll(containerIds).findAll().collect {unwrapIfProxy(it)} } @Override @@ -79,14 +79,21 @@ class ClassifierService extends ContainerService { Classifier.findAllByReadableByAuthenticatedUsers(true) } + @Override Classifier get(Serializable id) { Classifier.get(id) } - List list(Map pagination = [:]) { + @Override + List list(Map pagination) { Classifier.list(pagination) } + @Override + List list() { + Classifier.list().collect {unwrapIfProxy(it)} + } + @Override List findAllContainersInside(UUID containerId) { Classifier.findAllContainedInClassifierId(containerId) diff --git a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/FolderService.groovy b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/FolderService.groovy index 107b8e91d3..40e010fc8b 100644 --- a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/FolderService.groovy +++ b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/FolderService.groovy @@ -17,7 +17,6 @@ */ package uk.ac.ox.softeng.maurodatamapper.core.container - import uk.ac.ox.softeng.maurodatamapper.core.facet.Rule import uk.ac.ox.softeng.maurodatamapper.core.model.ContainerService import uk.ac.ox.softeng.maurodatamapper.core.model.Model @@ -69,7 +68,7 @@ class FolderService extends ContainerService { @Override List getAll(Collection containerIds) { - Folder.getAll(containerIds) + Folder.getAll(containerIds).findAll().collect {unwrapIfProxy(it)} } @Override @@ -121,10 +120,16 @@ class FolderService extends ContainerService { null } - List list(Map pagination = [:]) { + @Override + List list(Map pagination) { Folder.list(pagination) } + @Override + List list() { + Folder.list().collect {unwrapIfProxy(it)} + } + Long count() { Folder.count() } @@ -246,7 +251,7 @@ class FolderService extends ContainerService { copy.label = original.label copy.description = original.description - metadataService.findAllByMultiFacetAwareItemId(original.id).each {copy.addToMetadata(it.namespace, it.key, it.value, copier)} + metadataService.findAllByMultiFacetAwareItemId(original.id).each {copy.addToMetadata(it.namespace, it.key, it.value, copier.emailAddress)} ruleService.findAllByMultiFacetAwareItemId(original.id).each {rule -> Rule copiedRule = new Rule(name: rule.name, description: rule.description, createdBy: copier.emailAddress) rule.ruleRepresentations.each {ruleRepresentation -> diff --git a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderService.groovy b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderService.groovy index b10a6ef57e..a24c666434 100644 --- a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderService.groovy +++ b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderService.groovy @@ -102,7 +102,7 @@ class VersionedFolderService extends ContainerService implement @Override List getAll(Collection containerIds) { - VersionedFolder.getAll(containerIds) + VersionedFolder.getAll(containerIds).findAll().collect {unwrapIfProxy(it)} } @Override @@ -275,10 +275,16 @@ class VersionedFolderService extends ContainerService implement null } - List list(Map pagination = [:]) { + @Override + List list(Map pagination) { VersionedFolder.list(pagination) } + @Override + List list() { + VersionedFolder.list().collect {unwrapIfProxy(it)} + } + Long count() { VersionedFolder.count() } diff --git a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/facet/MetadataService.groovy b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/facet/MetadataService.groovy index 7416271d60..025379b734 100644 --- a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/facet/MetadataService.groovy +++ b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/facet/MetadataService.groovy @@ -67,7 +67,7 @@ class MetadataService implements MultiFacetItemAwareService { } void copy(MultiFacetAware target, Metadata item, UserSecurityPolicyManager userSecurityPolicyManager) { - target.addToMetadata(item.namespace, item.key, item.value, userSecurityPolicyManager.user) + target.addToMetadata(item.namespace, item.key, item.value, userSecurityPolicyManager.user.emailAddress) } @Override diff --git a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/provider/MauroDataMapperServiceProviderService.groovy b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/provider/MauroDataMapperServiceProviderService.groovy index 7559afe872..28e4c73abe 100644 --- a/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/provider/MauroDataMapperServiceProviderService.groovy +++ b/mdm-core/grails-app/services/uk/ac/ox/softeng/maurodatamapper/core/provider/MauroDataMapperServiceProviderService.groovy @@ -107,6 +107,15 @@ class MauroDataMapperServiceProviderService extends MauroDataMapperProviderServi static T findService(Set beans, String namespace, String name, String version) { + if (!version) { + // return the latest version of the service if theres more than 1 + return beans.findAll { + it.namespace.equalsIgnoreCase(namespace) && + it.name.equalsIgnoreCase(name) + } + .sort() + .last() + } beans.find { it.namespace.equalsIgnoreCase(namespace) && it.name.equalsIgnoreCase(name) && diff --git a/mdm-core/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderFunctionalSpec.groovy b/mdm-core/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderFunctionalSpec.groovy index 8e65cefc59..8bd46145ab 100644 --- a/mdm-core/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderFunctionalSpec.groovy +++ b/mdm-core/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/core/container/VersionedFolderFunctionalSpec.groovy @@ -99,7 +99,7 @@ class VersionedFolderFunctionalSpec extends ResourceFunctionalSpec extends CatalogueItemController< T oldestAncestor = modelService.findOldestAncestor(instance) as T List versionTreeModelList = modelService.buildModelVersionTree(oldestAncestor, null, - null, true, + null, true, false, currentUserSecurityPolicyManager) respond versionTreeModelList } @@ -682,10 +682,16 @@ abstract class ModelController extends CatalogueItemController< T oldestAncestor = modelService.findOldestAncestor(instance) as T + boolean branchesOnly = params.boolean('branchesOnly', false) + boolean forMerge = params.boolean('forMerge', false) + List versionTreeModelList = modelService.buildModelVersionTree(oldestAncestor, null, null, false, + branchesOnly || forMerge, currentUserSecurityPolicyManager) - respond versionTreeModelList.findAll {!it.newFork} + respond versionTreeModelList.findAll { + (!(it.newFork || (forMerge && it.id == instance.id.toString()))) + } } @Override diff --git a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItem.groovy b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItem.groovy index 43bc7d0239..b939581b46 100644 --- a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItem.groovy +++ b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItem.groovy @@ -17,6 +17,7 @@ */ package uk.ac.ox.softeng.maurodatamapper.core.model + import uk.ac.ox.softeng.maurodatamapper.core.diff.Diffable import uk.ac.ox.softeng.maurodatamapper.core.diff.ObjectDiff import uk.ac.ox.softeng.maurodatamapper.core.facet.Annotation @@ -112,13 +113,14 @@ trait CatalogueItem implements InformationAware, EditHistory static PaginatedLuceneResult luceneStandardSearch(Class clazz, String searchTerm, List allowedIds, Map pagination, @DelegatesTo(HibernateSearchApi) Closure additional = null) { + Lucene.securedPaginatedList(clazz, allowedIds, pagination) { if (searchTerm) { simpleQueryString(searchTerm, 'label', 'description', 'aliasesString', 'metadata.key', 'metadata.value') } if (additional) { additional.setResolveStrategy(Closure.DELEGATE_FIRST) - additional.setDelegate(delegate) + additional.setDelegate(getDelegate()) additional.call() } } diff --git a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItemService.groovy b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItemService.groovy index a0a30e2012..e0dabbeece 100644 --- a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItemService.groovy +++ b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/CatalogueItemService.groovy @@ -29,6 +29,7 @@ import uk.ac.ox.softeng.maurodatamapper.core.facet.RuleService import uk.ac.ox.softeng.maurodatamapper.core.facet.SemanticLinkService import uk.ac.ox.softeng.maurodatamapper.core.facet.SemanticLinkType import uk.ac.ox.softeng.maurodatamapper.core.rest.transport.model.CopyInformation +import uk.ac.ox.softeng.maurodatamapper.core.facet.rule.RuleRepresentation import uk.ac.ox.softeng.maurodatamapper.core.rest.transport.model.MergeFieldDiffData import uk.ac.ox.softeng.maurodatamapper.core.traits.service.DomainService import uk.ac.ox.softeng.maurodatamapper.core.traits.service.MultiFacetAwareService @@ -144,7 +145,7 @@ abstract class CatalogueItemService implements DomainSe CopyInformation copyInformation = new CopyInformation()) { copy = populateCopyData(original, copy, copier, copyInformation) classifierService.findAllByCatalogueItemId(userSecurityPolicyManager, original.id).each { copy.addToClassifiers(it) } - metadataService.findAllByMultiFacetAwareItemId(original.id).each { copy.addToMetadata(it.namespace, it.key, it.value, copier) } + metadataService.findAllByMultiFacetAwareItemId(original.id).each { copy.addToMetadata(it.namespace, it.key, it.value, copier.emailAddress) } ruleService.findAllByMultiFacetAwareItemId(original.id).each { rule -> Rule copiedRule = new Rule(name: rule.name, description: rule.description, createdBy: copier.emailAddress) rule.ruleRepresentations.each { ruleRepresentation -> diff --git a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/ModelService.groovy b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/ModelService.groovy index f7b222fa0e..559b44fc06 100644 --- a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/ModelService.groovy +++ b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/ModelService.groovy @@ -124,11 +124,13 @@ abstract class ModelService extends CatalogueItemService imp abstract void delete(K model, boolean permanent, boolean flush) - abstract int countByLabel(String label) + abstract int countByAuthorityAndLabelAndBranchNameAndNotFinalised(Authority authority, String label, String branchName) - abstract List findAllByLabel(String label) + abstract int countByAuthorityAndLabelAndVersion(Authority authority, String label, Version modelVersion) - abstract int countAllByLabelAndBranchNameAndNotFinalised(String label, String branchName) + abstract int countByAuthorityAndLabel(Authority authority, String label) + + abstract List findAllByAuthorityAndLabel(Authority authority, String label) abstract K copyModel(K original, Folder folderToCopyInto, @@ -195,7 +197,7 @@ abstract class ModelService extends CatalogueItemService imp findAllReadableModels(constrainedIds, includeDeleted) } - K finaliseModel(K model, User user, Version modelVersion, VersionChangeType versionChangeType, + K finaliseModel(K model, User user, Version requestedModelVersion, VersionChangeType versionChangeType, String versionTag) { log.debug('Finalising model') long start = System.currentTimeMillis() @@ -205,7 +207,7 @@ abstract class ModelService extends CatalogueItemService imp // No requirement to have a breadcrumbtree breadcrumbTreeService.finalise(model.breadcrumbTree) - model.modelVersion = getNextModelVersion(model, modelVersion, versionChangeType) + model.modelVersion = getNextModelVersion(model, requestedModelVersion, versionChangeType) model.modelVersionTag = versionTag @@ -287,7 +289,7 @@ abstract class ModelService extends CatalogueItemService imp if (!newVersionCreationIsAllowed(model)) return model // Check if the branch name is already being used - if (countAllByLabelAndBranchNameAndNotFinalised(model.label, branchName) > 0) { + if (countByAuthorityAndLabelAndBranchNameAndNotFinalised(model.authority, model.label, branchName) > 0) { (model as GormValidateable).errors.reject('version.aware.label.branch.name.already.exists', ['branchName', getModelClass(), branchName, model.label] as Object[], 'Property [{0}] of class [{1}] with value [{2}] already exists for label [{3}]') @@ -295,7 +297,8 @@ abstract class ModelService extends CatalogueItemService imp } // We know at this point the datamodel is finalised which means its branch name == main so we need to check no unfinalised main branch exists - boolean draftModelOnMainBranchForLabel = countAllByLabelAndBranchNameAndNotFinalised(model.label, VersionAwareConstraints.DEFAULT_BRANCH_NAME) > 0 + boolean draftModelOnMainBranchForLabel = countByAuthorityAndLabelAndBranchNameAndNotFinalised(model.authority, model.label, + VersionAwareConstraints.DEFAULT_BRANCH_NAME) > 0 if (!draftModelOnMainBranchForLabel) { K newMainBranchModelVersion = copyModelAsNewBranchModel(model, @@ -411,12 +414,13 @@ abstract class ModelService extends CatalogueItemService imp List buildModelVersionTree(K instance, VersionLinkType versionLinkType, VersionTreeModel parentVersionTreeModel, - boolean includeForks, + boolean includeForks, boolean branchesOnly, UserSecurityPolicyManager userSecurityPolicyManager) { + if (!userSecurityPolicyManager.userCanReadSecuredResourceId(instance.class, instance.id)) return [] VersionTreeModel rootVersionTreeModel = new VersionTreeModel(instance, versionLinkType, parentVersionTreeModel) - List versionTreeModelList = [rootVersionTreeModel] + List versionTreeModelList = instance.finalised && branchesOnly ? [] : [rootVersionTreeModel] // If fork then add to the list but dont proceed any further into that tree if (versionLinkType == VersionLinkType.NEW_FORK_OF) return includeForks ? versionTreeModelList : [] @@ -425,7 +429,7 @@ abstract class ModelService extends CatalogueItemService imp versionLinks.each {link -> K linkedModel = get(link.multiFacetAwareItemId) versionTreeModelList. - addAll(buildModelVersionTree(linkedModel, link.linkType, rootVersionTreeModel, includeForks, userSecurityPolicyManager)) + addAll(buildModelVersionTree(linkedModel, link.linkType, rootVersionTreeModel, includeForks, branchesOnly, userSecurityPolicyManager)) } versionTreeModelList.sort() } @@ -567,20 +571,13 @@ abstract class ModelService extends CatalogueItemService imp catalogueItem } - Version getParentModelVersion(K currentModel) { - VersionLink versionLink = versionLinkService.findBySourceModelIdAndLinkType(currentModel.id, VersionLinkType.NEW_MODEL_VERSION_OF) - if (!versionLink) return null - Model parent = get(versionLink.targetModelId) - parent.modelVersion - } - Version getNextModelVersion(K model, Version requestedModelVersion, VersionChangeType requestedVersionChangeType) { if (requestedModelVersion) { // Prefer requested model version return requestedModelVersion } // We need to get the parent model version first so we can work out what to increment - Version parentModelVersion = getParentModelVersion(model) + Version parentModelVersion = getLatestModelVersionByLabel(model.label) if (!parentModelVersion) { // No parent model then set the current version to 0 to allow the first finalisation to be defined using the versionChangeType @@ -606,13 +603,20 @@ abstract class ModelService extends CatalogueItemService imp Version.nextMajorVersion(parentModelVersion) } - void checkfinaliseModel(K model, Boolean finalised) { - if (finalised && !model.finalised) { - model.finalised = finalised - model.dateFinalised = model.finalised ? OffsetDateTime.now() : null + void checkFinaliseModel(K model, Boolean finalise, Boolean importAsNewBranchModelVersion = false) { + if (finalise && (!model.finalised || !model.modelVersion)) { + // Parameter update will have set the model as finalised, but it wont have set the model version + // If the actual import data includes finalised data then it will also containt the model version + // If the model hasnt been imported as a new branch model version then we need to check if any existing models + // If existing models then we cant finalise as we need to link the imported model + if (!importAsNewBranchModelVersion && countByAuthorityAndLabel(model.authority, model.label)) { + throw new ApiBadRequestException('MSXX', 'Request to finalise import without creating newBranchModelVersion to existing models') + } + model.finalised = true } - if (model.finalised && !model.modelVersion) { - model.modelVersion = Version.from('1.0.0') + if (model.finalised) { + model.dateFinalised = model.dateFinalised ?: OffsetDateTime.now() + model.modelVersion = model.modelVersion ?: getNextModelVersion(model, null, VersionChangeType.MAJOR) } } @@ -625,8 +629,8 @@ abstract class ModelService extends CatalogueItemService imp void checkDocumentationVersion(K model, boolean importAsNewDocumentationVersion, User catalogueUser) { if (importAsNewDocumentationVersion) { - if (countByLabel(model.label)) { - List existingModels = findAllByLabel(model.label) + if (countByAuthorityAndLabel(model.authority, model.label)) { + List existingModels = findAllByAuthorityAndLabel(model.authority, model.label) existingModels.each {existing -> log.debug('Setting Model as new documentation version of [{}:{}]', existing.label, existing.documentationVersion) if (!existing.finalised) finaliseModel(existing, catalogueUser, null, null, null) @@ -642,8 +646,17 @@ abstract class ModelService extends CatalogueItemService imp void checkBranchModelVersion(K model, Boolean importAsNewBranchModelVersion, String branchName, User catalogueUser) { if (importAsNewBranchModelVersion) { - if (countByLabel(model.label)) { + if (countByAuthorityAndLabel(model.authority, model.label)) { K latest = findLatestFinalisedModelByLabel(model.label) + + if (!latest) { + log.info('No finalised model to create branch from so finalising existing main branch') + latest = findCurrentMainBranchByLabel(model.label) + finaliseModel(latest, catalogueUser, Version.from('1'), null, null) + save(latest, flush: true, validate: false) + } + + // Now we have a finalised model to work from if (latest) { setModelIsNewBranchModelVersionOfModel(model, latest, catalogueUser) model.dateFinalised = null @@ -652,7 +665,7 @@ abstract class ModelService extends CatalogueItemService imp model.branchName = branchName ?: VersionAwareConstraints.DEFAULT_BRANCH_NAME model.documentationVersion = Version.from('1') } else { - throw new ApiBadRequestException('MSXX', 'Request to importAsNewBranchModelVersion but no finalised model to use') + throw new ApiBadRequestException('MSXX', 'Request to importAsNewBranchModelVersion but no finalised model or main branch available') } } else log.info('Marked as importAsNewBranchModelVersion but no existing Models with label [{}]', model.label) } diff --git a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/facet/MetadataAware.groovy b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/facet/MetadataAware.groovy index f6bce378bc..5d74d8718e 100644 --- a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/facet/MetadataAware.groovy +++ b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/model/facet/MetadataAware.groovy @@ -59,6 +59,7 @@ trait MetadataAware { addToMetadata(new Metadata(args)) } + @Deprecated def addToMetadata(String namespace, String key, String value, User createdBy) { addToMetadata(namespace, key, value, createdBy.emailAddress) } diff --git a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/provider/importer/ModelImporterProviderService.groovy b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/provider/importer/ModelImporterProviderService.groovy index 146a27e05a..1e042bd485 100644 --- a/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/provider/importer/ModelImporterProviderService.groovy +++ b/mdm-core/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/core/provider/importer/ModelImporterProviderService.groovy @@ -59,10 +59,16 @@ abstract class ModelImporterProviderService { + final static HibernateProxyHandler HIBERNATE_PROXY_HANDLER = new HibernateProxyHandler() + abstract K get(Serializable id) abstract List list(Map args) @@ -35,4 +40,8 @@ trait DomainService { K save(Map args, K domain) { domain.save(args) } + + K unwrapIfProxy(def ge) { + HIBERNATE_PROXY_HANDLER.unwrapIfProxy(ge) as K + } } \ No newline at end of file diff --git a/mdm-plugin-authentication-apikey/grails-app/conf/application.yml b/mdm-plugin-authentication-apikey/grails-app/conf/application.yml index 2363d52b96..9bc2b9bb65 100644 --- a/mdm-plugin-authentication-apikey/grails-app/conf/application.yml +++ b/mdm-plugin-authentication-apikey/grails-app/conf/application.yml @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: diff --git a/mdm-plugin-authentication-apikey/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/authentication/apikey/ApiKeyAuthenticationInterceptor.groovy b/mdm-plugin-authentication-apikey/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/authentication/apikey/ApiKeyAuthenticationInterceptor.groovy index 9042acca97..c7007457de 100644 --- a/mdm-plugin-authentication-apikey/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/authentication/apikey/ApiKeyAuthenticationInterceptor.groovy +++ b/mdm-plugin-authentication-apikey/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/authentication/apikey/ApiKeyAuthenticationInterceptor.groovy @@ -39,9 +39,6 @@ class ApiKeyAuthenticationInterceptor implements SecurityPolicyManagerIntercepto } boolean before() { - - checkSessionIsValid() - if (!securityPolicyManagerIsSet()) { String apikeyHeader = request.getHeader(API_KEY_HEADER) // No header then just carry on as if a normal request diff --git a/mdm-plugin-authentication-basic/grails-app/conf/application.yml b/mdm-plugin-authentication-basic/grails-app/conf/application.yml index 94597ee7f3..e7f37348eb 100644 --- a/mdm-plugin-authentication-basic/grails-app/conf/application.yml +++ b/mdm-plugin-authentication-basic/grails-app/conf/application.yml @@ -113,7 +113,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: diff --git a/mdm-plugin-dataflow/build.gradle b/mdm-plugin-dataflow/build.gradle index 8a92588e75..e8b8a3359d 100644 --- a/mdm-plugin-dataflow/build.gradle +++ b/mdm-plugin-dataflow/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath "org.grails:grails-gradle-plugin:$grailsVersion" classpath "org.grails.plugins:hibernate5:$grailsHibernate5Version" classpath "org.grails.plugins:views-gradle:$grailsViewsVersion" + classpath "com.bertramlabs.plugins:asset-pipeline-gradle:$assetPipelineVersion" + } } @@ -24,6 +26,7 @@ apply plugin: "org.grails.grails-plugin" apply plugin: "org.grails.grails-plugin-publish" apply plugin: "org.grails.plugins.views-json" apply plugin: "org.grails.plugins.views-markup" +apply plugin: "com.bertramlabs.asset-pipeline" apply plugin: 'ox.softeng.grails' @@ -67,6 +70,7 @@ dependencies { compile "org.grails.plugins:views-json:$grailsViewsVersion" compile "org.grails.plugins:views-json-templates:$grailsViewsVersion" compile "org.grails.plugins:views-markup:$grailsViewsVersion" + compile "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" compileOnly "io.micronaut:micronaut-inject-groovy" console "org.grails:grails-console" // Until the fixes are published we need to use our versions of the profiles diff --git a/mdm-plugin-dataflow/grails-app/conf/application.yml b/mdm-plugin-dataflow/grails-app/conf/application.yml index 039086adb8..d02cd72e83 100644 --- a/mdm-plugin-dataflow/grails-app/conf/application.yml +++ b/mdm-plugin-dataflow/grails-app/conf/application.yml @@ -131,7 +131,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -141,6 +141,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: diff --git a/mdm-plugin-datamodel/build.gradle b/mdm-plugin-datamodel/build.gradle index 7105860b58..1925fd6168 100644 --- a/mdm-plugin-datamodel/build.gradle +++ b/mdm-plugin-datamodel/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath "org.grails:grails-gradle-plugin:$grailsVersion" classpath "org.grails.plugins:hibernate5:$grailsHibernate5Version" classpath "org.grails.plugins:views-gradle:$grailsViewsVersion" + classpath "com.bertramlabs.plugins:asset-pipeline-gradle:$assetPipelineVersion" + } } @@ -24,6 +26,7 @@ apply plugin: "org.grails.grails-plugin" apply plugin: "org.grails.grails-plugin-publish" apply plugin: "org.grails.plugins.views-json" apply plugin: "org.grails.plugins.views-markup" +apply plugin: "com.bertramlabs.asset-pipeline" apply plugin: 'ox.softeng.grails' @@ -67,6 +70,7 @@ dependencies { compile "org.grails.plugins:views-json:$grailsViewsVersion" compile "org.grails.plugins:views-json-templates:$grailsViewsVersion" compile "org.grails.plugins:views-markup:$grailsViewsVersion" + compile "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" compileOnly "io.micronaut:micronaut-inject-groovy" console "org.grails:grails-console" // Until the fixes are published we need to use our versions of the profiles diff --git a/mdm-plugin-datamodel/grails-app/assets/bootstrap_data/versioningModelDescription.txt b/mdm-plugin-datamodel/grails-app/assets/bootstrap_data/versioningModelDescription.txt new file mode 100644 index 0000000000..6fb8d55868 --- /dev/null +++ b/mdm-plugin-datamodel/grails-app/assets/bootstrap_data/versioningModelDescription.txt @@ -0,0 +1,6 @@ +Baal is the nominal homeworld of the Blood Angels Chapter of the Adeptus Astartes located in the Baal System of the Segmentum Ultima. Baal itself is a dry, dusty Desert World, scarcely inhabited by anyone other than mutants and feral animals. +However its two moons, Baal Prime and Baal Secundus, support notable Human settlements and were once "paradises for mortal men" though now they are classified as savage Feral Worlds by the Imperium. + +Little is known about the past circumstances of these two moons, but at some time in the distant past, probably during the Age of Technology or the Age of Strife, a cataclysmic conflict arose and led to the widespread use of both viral and nuclear weapons of mass destruction, destroying the ecosystems of both satellites and contaminating their biospheres with intense radioactive fallout, chemical pollution and deadly biological agents. + +The mighty fortress-monastery of the Blood Angels, the Arx Angelicum, was built on the desert surface of the world of Baal proper, and since the time of Sanguinius, the Blood Angels have continued to recruit from among the tribes of the people known as "the Blood" on Baal Secundus and the tribes of Baal Prime, where a Human colony was reestablished shortly after the time of the Horus Heresy. \ No newline at end of file diff --git a/mdm-plugin-datamodel/grails-app/conf/application.yml b/mdm-plugin-datamodel/grails-app/conf/application.yml index a24adc1a79..8b62e3e5da 100644 --- a/mdm-plugin-datamodel/grails-app/conf/application.yml +++ b/mdm-plugin-datamodel/grails-app/conf/application.yml @@ -113,7 +113,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: @@ -150,6 +160,10 @@ dataSource: --- environments: test: + maurodatamapper: + authority: + name: 'Test Authority' + url: 'http://localhost' spring.flyway.enabled: false database: name: 'mpdTest' diff --git a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_11_0__add_model_data_type.sql b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_11_0__add_model_data_type.sql index 19effc6797..75d929b810 100644 --- a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_11_0__add_model_data_type.sql +++ b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_11_0__add_model_data_type.sql @@ -1,5 +1,5 @@ -ALTER TABLE maurodatamapper.datamodel.data_type +ALTER TABLE datamodel.data_type ADD COLUMN model_resource_id UUID NULL; -ALTER TABLE maurodatamapper.datamodel.data_type +ALTER TABLE datamodel.data_type ADD COLUMN model_resource_domain_type VARCHAR(255) NULL; \ No newline at end of file diff --git a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_9_0__make_sure_data_model_type_is_correct.sql b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_9_0__make_sure_data_model_type_is_correct.sql index 5c610f9a0a..09f71112f2 100644 --- a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_9_0__make_sure_data_model_type_is_correct.sql +++ b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V1_9_0__make_sure_data_model_type_is_correct.sql @@ -1,4 +1,4 @@ -UPDATE maurodatamapper.datamodel.data_model +UPDATE datamodel.data_model SET model_type = CASE WHEN model_type IN ('DATA_ASSET', 'Data Asset') THEN 'Data Asset' diff --git a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_10_0__fix_erroneous_new_model_of_and_wrong_direction_version_links.sql b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_10_0__fix_erroneous_new_model_of_and_wrong_direction_version_links.sql index b9b38f1e39..f3cc804e3b 100644 --- a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_10_0__fix_erroneous_new_model_of_and_wrong_direction_version_links.sql +++ b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_10_0__fix_erroneous_new_model_of_and_wrong_direction_version_links.sql @@ -1,13 +1,13 @@ -- Simple one, just update the link type to the correct fork type where the labels dont match WITH data AS ( SELECT vl.id AS vl_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.datamodel.data_model source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.datamodel.data_model target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN datamodel.data_model source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN datamodel.data_model target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'NEW_MODEL_VERSION_OF' AND source.label <> target.label ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_FORK_OF' FROM data WHERE vl.id = data.vl_id; @@ -18,12 +18,12 @@ WITH data AS ( SELECT vl.id AS vl_id, source.id AS new_target_id, target.id AS new_source_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.datamodel.data_model source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.datamodel.data_model target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN datamodel.data_model source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN datamodel.data_model target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'SUPERSEDED_BY_MODEL' ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_FORK_OF', target_model_id = new_target_id, multi_facet_aware_item_id = new_source_id @@ -35,12 +35,12 @@ WHERE data.vl_id = vl.id WITH data AS ( SELECT vl.id AS vl_id, vl.multi_facet_aware_item_id AS new_source_id - FROM maurodatamapper.datamodel.join_datamodel_to_facet jt - INNER JOIN maurodatamapper.core.version_link vl ON jt.version_link_id = vl.id + FROM datamodel.join_datamodel_to_facet jt + INNER JOIN core.version_link vl ON jt.version_link_id = vl.id WHERE vl.link_type = 'NEW_FORK_OF' AND jt.datamodel_id <> vl.multi_facet_aware_item_id ) -UPDATE maurodatamapper.datamodel.join_datamodel_to_facet jt +UPDATE datamodel.join_datamodel_to_facet jt SET datamodel_id = data.new_source_id FROM data WHERE jt.datamodel_id <> new_source_id AND @@ -53,12 +53,12 @@ WITH data AS ( SELECT vl.id AS vl_id, source.id AS new_target_id, target.id AS new_source_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.datamodel.data_model source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.datamodel.data_model target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN datamodel.data_model source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN datamodel.data_model target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'SUPERSEDED_BY_DOCUMENTATION' ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_DOCUMENTATION_VERSION_OF', target_model_id = new_target_id, multi_facet_aware_item_id = new_source_id @@ -70,12 +70,12 @@ WHERE data.vl_id = vl.id WITH data AS ( SELECT vl.id AS vl_id, vl.multi_facet_aware_item_id AS new_source_id - FROM maurodatamapper.datamodel.join_datamodel_to_facet jt - INNER JOIN maurodatamapper.core.version_link vl ON jt.version_link_id = vl.id + FROM datamodel.join_datamodel_to_facet jt + INNER JOIN core.version_link vl ON jt.version_link_id = vl.id WHERE vl.link_type = 'NEW_DOCUMENTATION_VERSION_OF' AND jt.datamodel_id <> vl.multi_facet_aware_item_id ) -UPDATE maurodatamapper.datamodel.join_datamodel_to_facet jt +UPDATE datamodel.join_datamodel_to_facet jt SET datamodel_id = data.new_source_id FROM data WHERE jt.datamodel_id <> new_source_id AND diff --git a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_11_0__update_database_metadata_values.sql b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_11_0__update_database_metadata_values.sql new file mode 100644 index 0000000000..411286525b --- /dev/null +++ b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/V2_11_0__update_database_metadata_values.sql @@ -0,0 +1,112 @@ +CREATE SCHEMA IF NOT EXISTS migration; + +CREATE TABLE migration.database_metadata ( + id UUID NOT NULL PRIMARY KEY, + version BIGINT NOT NULL, + date_created TIMESTAMP NOT NULL, + last_updated TIMESTAMP NOT NULL, + multi_facet_aware_item_domain_type VARCHAR(255) NOT NULL, + namespace TEXT NOT NULL, + multi_facet_aware_item_id UUID, + value TEXT NOT NULL, + created_by VARCHAR(255) NOT NULL, + key TEXT NOT NULL +); + +CREATE TABLE migration.additional_metadata ( + id UUID NOT NULL PRIMARY KEY, + version BIGINT NOT NULL, + date_created TIMESTAMP NOT NULL, + last_updated TIMESTAMP NOT NULL, + multi_facet_aware_item_domain_type VARCHAR(255) NOT NULL, + namespace TEXT NOT NULL, + multi_facet_aware_item_id UUID, + value TEXT NOT NULL, + created_by VARCHAR(255) NOT NULL, + key TEXT NOT NULL +); + + +-- Extract all metadata for databases for speed +INSERT INTO migration.database_metadata +SELECT * +FROM core.metadata +WHERE namespace LIKE 'ox.softeng.metadatacatalogue.plugins.database%' AND + "key" LIKE '%[%]%'; + +-- Extract out the name of MD like 'foreign_key[dhsjdhs]' +INSERT +INTO migration.additional_metadata(id, version, date_created, last_updated, multi_facet_aware_item_domain_type, namespace, multi_facet_aware_item_id, "value", created_by, + "key") +SELECT uuid_generate_v1(), + version, + date_created, + last_updated, + multi_facet_aware_item_domain_type, + REGEXP_REPLACE(namespace, '^ox.softeng.metadatacatalogue.plugins.database', 'uk.ac.ox.softeng.metadatacatalogue.plugins.database'), + multi_facet_aware_item_id, + SUBSTRING("key", '.+?\[(.+)\]'), + created_by, + CONCAT(SUBSTRING("key", '(.+?)\[.+\]'), '_name') +FROM migration.database_metadata; + +-- Migrate the namespace +UPDATE core.metadata +SET namespace = CONCAT('uk.ac.', namespace) +WHERE namespace LIKE 'ox.softeng.metadatacatalogue.plugins.database%'; + +-- Remove the constraint name from the existing key values in metadata +UPDATE core.metadata +SET "key"= CONCAT(SUBSTRING("key", '(.+?)\[.+\]'), '_columns') +WHERE namespace LIKE 'uk.ac.ox.softeng.metadatacatalogue.plugins.database%' AND + "key" LIKE '%[%]%'; + +-- Insert new rows +INSERT INTO core.metadata(id, version, date_created, last_updated, multi_facet_aware_item_domain_type, namespace, multi_facet_aware_item_id, "value", created_by, "key") +SELECT id, + version, + date_created, + last_updated, + multi_facet_aware_item_domain_type, + namespace, + multi_facet_aware_item_id, + "value", + created_by, + "key" +FROM migration.additional_metadata; + +-- Add the new rows to the facet join tables +-- Only databases are imported like this +INSERT INTO datamodel.join_datamodel_to_facet(datamodel_id, metadata_id) +SELECT multi_facet_aware_item_id, + id +FROM migration.additional_metadata +WHERE multi_facet_aware_item_domain_type = 'DataModel'; + +INSERT INTO datamodel.join_dataclass_to_facet(dataclass_id, metadata_id) +SELECT multi_facet_aware_item_id, + id +FROM migration.additional_metadata +WHERE multi_facet_aware_item_domain_type = 'DataClass'; + +INSERT INTO datamodel.join_dataelement_to_facet(dataelement_id, metadata_id) +SELECT multi_facet_aware_item_id, + id +FROM migration.additional_metadata +WHERE multi_facet_aware_item_domain_type = 'DataElement'; + +INSERT INTO datamodel.join_datatype_to_facet(datatype_id, metadata_id) +SELECT multi_facet_aware_item_id, + id +FROM migration.additional_metadata +WHERE multi_facet_aware_item_domain_type IN ('PrimitiveType', 'ReferenceType', 'EnumerationType'); + +INSERT INTO datamodel.join_enumerationvalue_to_facet(enumerationvalue_id, metadata_id) +SELECT multi_facet_aware_item_id, + id +FROM migration.additional_metadata +WHERE multi_facet_aware_item_domain_type = 'EnumerationValue'; + +-- Cleanup +DROP TABLE migration.database_metadata; +DROP TABLE migration.additional_metadata; diff --git a/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/beforeValidate.sql b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/beforeValidate.sql new file mode 100644 index 0000000000..f0bd2745bb --- /dev/null +++ b/mdm-plugin-datamodel/grails-app/conf/db/migration/datamodel/beforeValidate.sql @@ -0,0 +1,14 @@ +UPDATE datamodel.flyway_schema_history +SET checksum = -1826314098 +WHERE version = '1.9.0' AND + checksum = -809313812; + +UPDATE datamodel.flyway_schema_history +SET checksum = -1282220622 +WHERE version = '1.11.0' AND + checksum = 404844351; + +UPDATE datamodel.flyway_schema_history +SET checksum = -440622816 +WHERE version = '2.10.0' AND + checksum = 446604726; \ No newline at end of file diff --git a/mdm-plugin-datamodel/grails-app/init/uk/ac/ox/softeng/maurodatamapper/datamodel/BootStrap.groovy b/mdm-plugin-datamodel/grails-app/init/uk/ac/ox/softeng/maurodatamapper/datamodel/BootStrap.groovy index de7b70976b..c3eee81828 100644 --- a/mdm-plugin-datamodel/grails-app/init/uk/ac/ox/softeng/maurodatamapper/datamodel/BootStrap.groovy +++ b/mdm-plugin-datamodel/grails-app/init/uk/ac/ox/softeng/maurodatamapper/datamodel/BootStrap.groovy @@ -20,8 +20,13 @@ package uk.ac.ox.softeng.maurodatamapper.datamodel import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority import uk.ac.ox.softeng.maurodatamapper.core.authority.AuthorityService import uk.ac.ox.softeng.maurodatamapper.core.container.Folder +import uk.ac.ox.softeng.maurodatamapper.core.facet.MetadataService +import uk.ac.ox.softeng.maurodatamapper.core.facet.RuleService import uk.ac.ox.softeng.maurodatamapper.datamodel.bootstrap.BootstrapModels +import uk.ac.ox.softeng.maurodatamapper.datamodel.item.DataClassService +import uk.ac.ox.softeng.maurodatamapper.datamodel.item.DataElementService +import asset.pipeline.grails.AssetResourceLocator import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.MessageSource @@ -36,7 +41,17 @@ class BootStrap { DataModelService dataModelService - def init = {servletContext -> + DataElementService dataElementService + + DataClassService dataClassService + + MetadataService metadataService + + RuleService ruleService + + AssetResourceLocator assetResourceLocator + + def init = { servletContext -> log.debug('Main bootstrap complete') @@ -55,7 +70,8 @@ class BootStrap { BootstrapModels.buildAndSaveFinalisedSimpleDataModel(messageSource, folder, authority) } if (DataModel.countByLabel(BootstrapModels.MODEL_VERSION_TREE_DATAMODEL_NAME) == 0) { - BootstrapModels.buildAndSaveModelVersionTree(messageSource, folder, authority, dataModelService) + BootstrapModels.buildAndSaveModelVersionTree(messageSource, folder, authority, dataModelService, dataClassService, + dataElementService, assetResourceLocator) } if (DataModel.countByAuthorityIsNull() != 0) { log.warn('DataModels missing authority, updating with default authority') diff --git a/mdm-plugin-datamodel/grails-app/services/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelService.groovy b/mdm-plugin-datamodel/grails-app/services/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelService.groovy index 4add1b318b..10795a4f7b 100644 --- a/mdm-plugin-datamodel/grails-app/services/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelService.groovy +++ b/mdm-plugin-datamodel/grails-app/services/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelService.groovy @@ -82,14 +82,19 @@ class DataModelService extends ModelService implements SummaryMetadat @Override List getAll(Collection ids) { - DataModel.getAll(ids).findAll() + DataModel.getAll(ids).findAll().collect {unwrapIfProxy(it)} } @Override - List list(Map pagination = [:]) { + List list(Map pagination) { DataModel.list(pagination) } + @Override + List list() { + DataModel.list().collect {unwrapIfProxy(it)} + } + @Override boolean handlesPathPrefix(String pathPrefix) { pathPrefix == "dm" @@ -104,14 +109,14 @@ class DataModelService extends ModelService implements SummaryMetadat * DataModel allows the import of DataType and DataClass * @Override - List domainImportableModelItemClasses() {[DataType, DataClass, PrimitiveType, EnumerationType, ReferenceType]} + List domainImportableModelItemClasses() {[DataType, DataClass, PrimitiveType, EnumerationType, ReferenceType]} */ Long count() { DataModel.count() } - int countByLabel(String label) { - DataModel.countByLabel(label) + int countByAuthorityAndLabel(Authority authority, String label) { + DataModel.countByAuthorityAndLabel(authority, label) } DataModel validate(DataModel dataModel) { @@ -121,7 +126,7 @@ class DataModelService extends ModelService implements SummaryMetadat if (dataModel.hasErrors()) { Errors existingErrors = dataModel.errors Errors cleanedErrors = new ValidationErrors(dataModel) - existingErrors.fieldErrors.each { fe -> + existingErrors.fieldErrors.each {fe -> if (!fe.field.contains('dataModel')) { cleanedErrors.rejectValue(fe.field, fe.code, fe.arguments, fe.defaultMessage) } @@ -417,8 +422,8 @@ class DataModelService extends ModelService implements SummaryMetadat } @Override - List findAllByLabel(String label) { - DataModel.findAllByLabel(label) + List findAllByAuthorityAndLabel(Authority authority, String label) { + DataModel.findAllByAuthorityAndLabel(authority, label) } @Override @@ -449,8 +454,14 @@ class DataModelService extends ModelService implements SummaryMetadat DataModel.byDeleted().list(pagination) as List } - int countAllByLabelAndBranchNameAndNotFinalised(String label, String branchName) { - DataModel.countByLabelAndBranchNameAndFinalised(label, branchName, false) + @Override + int countByAuthorityAndLabelAndBranchNameAndNotFinalised(Authority authority, String label, String branchName) { + DataModel.countByAuthorityAndLabelAndBranchNameAndFinalised(authority, label, branchName, false) + } + + @Override + int countByAuthorityAndLabelAndVersion(Authority authority, String label, Version modelVersion) { + DataModel.countByAuthorityAndLabelAndModelVersion(authority, label, modelVersion) } DataModel findLatestByDataLoaderPlugin(DataLoaderProviderService dataLoaderProviderService) { diff --git a/mdm-plugin-datamodel/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/datamodel/bootstrap/BootstrapModels.groovy b/mdm-plugin-datamodel/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/datamodel/bootstrap/BootstrapModels.groovy index a59cf7fbc4..7ecad5be37 100644 --- a/mdm-plugin-datamodel/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/datamodel/bootstrap/BootstrapModels.groovy +++ b/mdm-plugin-datamodel/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/datamodel/bootstrap/BootstrapModels.groovy @@ -20,12 +20,16 @@ package uk.ac.ox.softeng.maurodatamapper.datamodel.bootstrap import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority import uk.ac.ox.softeng.maurodatamapper.core.container.Classifier import uk.ac.ox.softeng.maurodatamapper.core.container.Folder +import uk.ac.ox.softeng.maurodatamapper.core.facet.Metadata +import uk.ac.ox.softeng.maurodatamapper.core.facet.Rule import uk.ac.ox.softeng.maurodatamapper.core.facet.SemanticLink import uk.ac.ox.softeng.maurodatamapper.core.facet.SemanticLinkType import uk.ac.ox.softeng.maurodatamapper.datamodel.DataModel import uk.ac.ox.softeng.maurodatamapper.datamodel.DataModelService import uk.ac.ox.softeng.maurodatamapper.datamodel.item.DataClass +import uk.ac.ox.softeng.maurodatamapper.datamodel.item.DataClassService import uk.ac.ox.softeng.maurodatamapper.datamodel.item.DataElement +import uk.ac.ox.softeng.maurodatamapper.datamodel.item.DataElementService import uk.ac.ox.softeng.maurodatamapper.datamodel.item.datatype.EnumerationType import uk.ac.ox.softeng.maurodatamapper.datamodel.item.datatype.PrimitiveType import uk.ac.ox.softeng.maurodatamapper.datamodel.item.datatype.ReferenceType @@ -34,8 +38,10 @@ import uk.ac.ox.softeng.maurodatamapper.security.UserSecurityPolicyManager import uk.ac.ox.softeng.maurodatamapper.security.basic.PublicAccessSecurityPolicyManager import uk.ac.ox.softeng.maurodatamapper.util.Version +import asset.pipeline.grails.AssetResourceLocator import groovy.util.logging.Slf4j import org.springframework.context.MessageSource +import org.springframework.core.io.Resource import java.time.OffsetDateTime import java.time.ZoneOffset @@ -46,9 +52,11 @@ import static uk.ac.ox.softeng.maurodatamapper.util.GormUtils.checkAndSave @Slf4j class BootstrapModels { + public static final String COMPLEX_DATAMODEL_NAME = 'Complex Test DataModel' public static final String SIMPLE_DATAMODEL_NAME = 'Simple Test DataModel' public static final String FINALISED_EXAMPLE_DATAMODEL_NAME = 'Finalised Example Test DataModel' + public static final String MODEL_VERSION_TREE_DATAMODEL_NAME = 'Model Version Tree DataModel' static DataModel buildAndSaveSimpleDataModel(MessageSource messageSource, Folder folder, Authority authority) { @@ -239,8 +247,9 @@ class BootstrapModels { simpleDataModel } - - static void buildAndSaveModelVersionTree(MessageSource messageSource, Folder folder, Authority authority, DataModelService dataModelService) { + static void buildAndSaveModelVersionTree(MessageSource messageSource, Folder folder, Authority authority, DataModelService dataModelService, + DataClassService dataClassService, DataElementService dataElementService, + AssetResourceLocator assetResourceLocator) { /* /- anotherFork v1 --------------------------- v2 -- v3 -- v4 --------------- v5 --- main @@ -254,9 +263,13 @@ class BootstrapModels { // V1 DataModel v1 = new DataModel(createdBy: DEVELOPMENT, label: MODEL_VERSION_TREE_DATAMODEL_NAME, + organisation: 'bootStrap', + author: 'bill', folder: folder, authority: authority) + checkAndSave(messageSource, v1) + v1 = populateV1(v1, messageSource) v1 = dataModelService.finaliseModel(v1, dev, Version.from('1'), null, null) checkAndSave(messageSource, v1) @@ -273,10 +286,14 @@ class BootstrapModels { // V2 main branch DataModel v2 = dataModelService.createNewBranchModelVersion('main', v1, dev, false, policyManager) checkAndSave(messageSource, v2) + v2 = modifyV2(v2, messageSource, assetResourceLocator, dataClassService, dataElementService) + checkAndSave(messageSource, v2) // newBranch from v1 (do this after is it creates the main branch if done before and then we have to hassle getting the id) DataModel newBranch = dataModelService.createNewBranchModelVersion('newBranch', v1, dev, false, policyManager) checkAndSave(messageSource, newBranch) + modifyNewBranch(newBranch, messageSource) + checkAndSave(messageSource, newBranch) // Finalise the main branch to v2 v2 = dataModelService.finaliseModel(v2, dev, Version.from('2'), null, null) @@ -296,6 +313,8 @@ class BootstrapModels { // testBranch from v3 (do this after is it creates the main branch if done before and then we have to hassle getting the id) DataModel testBranch = dataModelService.createNewBranchModelVersion('testBranch', v3, dev, false, policyManager) checkAndSave(messageSource, testBranch) + testBranch = modifyTestBranch(testBranch, messageSource) + checkAndSave(messageSource, testBranch) // Finalise main branch to v4 v4 = dataModelService.finaliseModel(v4, dev, Version.from('4'), null, null) @@ -318,9 +337,288 @@ class BootstrapModels { // Another branch DataModel anotherBranch = dataModelService.createNewBranchModelVersion('anotherBranch', v5, dev, false, policyManager) checkAndSave(messageSource, anotherBranch) + anotherBranch = modifyAnotherBranch(anotherBranch, messageSource) + checkAndSave(messageSource, anotherBranch) // Interesting branch DataModel interestingBranch = dataModelService.createNewBranchModelVersion('interestingBranch', v5, dev, false, policyManager) checkAndSave(messageSource, interestingBranch) } -} + + static DataModel populateV1(DataModel v1DataModel, MessageSource messageSource) { + + v1DataModel.addToMetadata(createdBy: DEVELOPMENT, namespace: 'v1Versioning.com', key: 'jun1', value: 'jun2') + .addToMetadata(createdBy: DEVELOPMENT, namespace: 'v1Versioning.com', key: 'mdk1', value: 'mdv1') + .addToMetadata(createdBy: DEVELOPMENT, namespace: 'v1Versioning.com', key: 'mdk2', value: 'mdv2') + + PrimitiveType v1PrimitiveType1 = new PrimitiveType(createdBy: DEVELOPMENT, + label: 'V1 Data Type') + v1DataModel.addToDataTypes(v1PrimitiveType1) + DataElement v1DataElement1 = new DataElement(createdBy: DEVELOPMENT, + label: 'V1 Data Element', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v1PrimitiveType1) + DataElement v1DataElement2 = new DataElement(createdBy: DEVELOPMENT, + label: 'V1 Second DataElement', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v1PrimitiveType1) + DataElement v1ModifyDataElement3 = new DataElement(createdBy: DEVELOPMENT, + label: 'V1 Modify DataElement', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v1PrimitiveType1) + DataElement v1ModifyDataElement4 = new DataElement(createdBy: DEVELOPMENT, + label: 'V1 Modify DataElement 2', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v1PrimitiveType1) + DataClass v1DataClass = new DataClass(createdBy: DEVELOPMENT, + label: 'V1 Data Class') + DataClass v1InternalDataClass = new DataClass(createdBy: DEVELOPMENT, + label: 'V1 Internal Data Class') + DataClass v1ModifyDataClass = new DataClass(createdBy: DEVELOPMENT, + label: 'V1 Modify Data Class') + + v1InternalDataClass.addToDataElements(v1DataElement1) + v1ModifyDataClass.addToDataElements(v1ModifyDataElement3).addToDataElements(v1ModifyDataElement4) + v1DataClass.addToDataElements(v1DataElement2) + .addToDataClasses(v1InternalDataClass) + + v1DataModel + .addToDataClasses(v1DataClass) + .addToDataClasses(v1ModifyDataClass) + .addToDataClasses(createdBy: DEVELOPMENT, label: 'V1 Another Data Class') + .addToDataTypes(v1PrimitiveType1) + + checkAndSave(messageSource, v1DataModel) + + v1DataModel.addToRules(name: "Bootstrapped versioning Test Rule", + description: "versioning Model rule Description", + createdBy: DEVELOPMENT) + + v1DataModel.addToRules(name: "Bootstrapped modify rule", + description: "Bootstrapped rule for modification", + createdBy: DEVELOPMENT) + + + v1DataModel.addToMetadata(namespace: 'versioning.com', key: 'map', value: ''' + /- anotherFork +v1 --------------------------- v2 -- v3 -- v4 --------------- v5 --- main + \\\\_ newBranch (v1) \\_ testBranch (v3) \\__ anotherBranch (v5) + \\_ fork ---- main \\_ interestingBranch (v5) + ''') + + + checkAndSave(messageSource, v1DataModel) + + return v1DataModel + + } + + static DataModel modifyV2(DataModel v2DataModel, MessageSource messageSource, AssetResourceLocator assetResourceLocator, DataClassService dataClassService, + DataElementService dataElementService) { + v2DataModel.addToMetadata(createdBy: DEVELOPMENT, namespace: 'JRDev.com/versioning', key: 'mkv1', value: 'mkv2') + .addToMetadata(createdBy: DEVELOPMENT, namespace: 'JRDev.com/versioning', key: 'abc2', value: 'abc3') + + v2DataModel.setAuthor('Dante') + v2DataModel.setOrganisation('Baal') + + Resource resource = assetResourceLocator.findAssetForURI('versioningModelDescription.txt') + + try {v2DataModel.setDescription(resource.getInputStream().getText())} + catch (NullPointerException e) { + v2DataModel.setDescription('default description due to error reading file. see log') + log.debug( + 'error reading the description file, please check the asset pipeline and ensure the versioningModelDescription.txt file is in the' + + 'available assets') + } + + PrimitiveType v2PrimitiveType1 = new PrimitiveType(createdBy: DEVELOPMENT, + label: 'V2 Data Type') + PrimitiveType v2PrimitiveType2 = new PrimitiveType(createdBy: DEVELOPMENT, + label: 'V2 Data Type 2') + PrimitiveType v2PrimitiveType3 = new PrimitiveType(createdBy: DEVELOPMENT, + label: 'V2 Data Type 3') + DataElement v2DataElement1 = new DataElement(createdBy: DEVELOPMENT, + label: 'V2 Data Element', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v2PrimitiveType1) + DataElement v2DataElement2 = new DataElement(createdBy: DEVELOPMENT, + label: 'V2 Second DataElement', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v2PrimitiveType2) + DataElement v2DataElement3 = new DataElement(createdBy: DEVELOPMENT, + label: 'V2 Third DataElement', + minMultiplicity: 1, + maxMultiplicity: 1, + dataType: v2PrimitiveType3) + DataClass v2DataClass = new DataClass(createdBy: DEVELOPMENT, + label: 'V2 Data Class') + + v2DataClass.addToDataElements(v2DataElement1).addToDataElements(v2DataElement2).addToDataElements(v2DataElement3) + + v2DataModel + .addToDataClasses(v2DataClass) + .addToDataTypes(v2PrimitiveType1) + .addToDataTypes(v2PrimitiveType2) + .addToDataTypes(v2PrimitiveType3) + + checkAndSave(messageSource, v2DataModel) + + v2DataModel.addToRules(name: "Bootstrapped versioning V2Model Rule", + description: "versioning V2Model model Description", + createdBy: DEVELOPMENT) + + v2DataModel.addToRules(name: "Bootstrapped versioning Deletion Rule", + description: "versioning V2Model model for Deletion", + createdBy: DEVELOPMENT) + + checkAndSave(messageSource, v2DataModel) + + manipulateV2Rules(v2DataModel, messageSource) + manipulateV2DataElements(v2DataModel, messageSource, dataElementService) + manipulateV2DataClasses(v2DataModel, messageSource, dataClassService) + manipulateV2MetaData(v2DataModel, messageSource) + + v2DataModel + + } + + static void manipulateV2DataClasses(DataModel v2DataModel, MessageSource messageSource, DataClassService dataClassService) { + + DataClass v1DataClass = v2DataModel.getDataClasses().find {it.label == 'V1 Data Class'} + DataClass v1InternalDataClass = v1DataClass.getDataClasses().find {it.label == 'V1 Internal Data Class'} + DataClass v1ModifyDataClass = v2DataModel.dataClasses.find {it.label == 'V1 Modify Data Class'} + + //modify dataClass + v1DataClass.description = 'Modified this description for V2' + checkAndSave(messageSource, v1DataClass) + + dataClassService.delete(v1InternalDataClass) + + //Move internal DC, this counts as a deletion and an addition + DataClass moved = new DataClass(createdBy: DEVELOPMENT, + label: 'V1 Internal Data Class') + v1ModifyDataClass.addToDataClasses(moved) + v2DataModel.addToDataClasses(moved) + checkAndSave(messageSource, v1ModifyDataClass) + checkAndSave(messageSource, v2DataModel) + } + + + static void manipulateV2DataElements(DataModel v2DataModel, MessageSource messageSource, DataElementService dataElementService) { + + DataClass v1ModifyDataClass = v2DataModel.dataClasses.find {it.label == 'V1 Modify Data Class'} + + //Rename DataElement this counts as an addition and deletion + DataElement modifyDataElement1 = v1ModifyDataClass.dataElements.find {it.label == 'V1 Modify DataElement'} + modifyDataElement1.label = 'Modified Label On this element' + checkAndSave(messageSource, modifyDataElement1) + + //remove other data element + DataElement modifyDataElement2 = v1ModifyDataClass.dataElements.find {it.label == 'V1 Modify DataElement 2'} + dataElementService.delete(modifyDataElement2) + } + + static void manipulateV2MetaData(DataModel v2DataModel, MessageSource messageSource) { + + //modify metaData + Metadata md1 = v2DataModel.metadata.find {it.key == 'jun1'} + md1.value = 'mod1' + checkAndSave(messageSource, md1) + + //remove metaData + Metadata md2 = v2DataModel.metadata.find {it.key == 'mdk2'} + v2DataModel.metadata.remove(md2) + md2.delete() + checkAndSave(messageSource, v2DataModel) + } + + static void manipulateV2Rules(DataModel v2DataModel, MessageSource messageSource) { + + Rule modifyRule = v2DataModel.rules.find {it.name == 'Bootstrapped versioning V2Model Rule'} + modifyRule.description = 'Modified this description' + checkAndSave(messageSource, modifyRule) + + Rule deleteRule = v2DataModel.rules.find {it.name == 'Bootstrapped versioning Deletion Rule'} + v2DataModel.rules.remove(deleteRule) + checkAndSave(messageSource, v2DataModel) + } + + + static DataModel modifyNewBranch(DataModel newBranch, MessageSource messageSource) { + + newBranch + .addToMetadata(createdBy: DEVELOPMENT, namespace: 'versioning.com', key: 'mdk1', value: 'mdv1') + .addToMetadata(createdBy: DEVELOPMENT, namespace: 'versioning.com', key: 'mdk2', value: 'mdv2') + .addToMetadata(createdBy: DEVELOPMENT, namespace: 'versioning.com/bootstrap', key: 'mdk1', value: 'mdv2') + + .addToAnnotations(createdBy: DEVELOPMENT, label: 'versioning annotation 1') + .addToAnnotations(createdBy: DEVELOPMENT, label: 'versioning annotation 2', description: 'with description') + + .addToDataTypes(new PrimitiveType(createdBy: DEVELOPMENT, label: 'string')) + .addToDataTypes(new PrimitiveType(createdBy: DEVELOPMENT, label: 'integer')) + + .addToDataClasses(createdBy: DEVELOPMENT, label: 'emptyVersioningClass', description: 'dataclass with desc') + .addToDataTypes(new EnumerationType(createdBy: DEVELOPMENT, label: 'catdogfish') + .addToEnumerationValues(key: 'C', value: 'Cat', idx: 0) + .addToEnumerationValues(key: 'D', value: 'Dog', idx: 1) + .addToEnumerationValues(key: 'F', value: 'Fish', idx: 2)) + checkAndSave(messageSource, newBranch) + newBranch + + } + + static DataModel modifyTestBranch(DataModel testBranch, MessageSource messageSource) { + DataClass v1DataClass = testBranch.getDataClasses().find {it.label == 'V1 Data Class'} + + v1DataClass.description = 'Modified this description for test branch' + checkAndSave(messageSource, v1DataClass) + + + DataElement de = v1DataClass.dataElements.find {it.label == 'V1 Second DataElement'} + de.description = 'Adding a description' + de.minMultiplicity = 0 + checkAndSave(messageSource, de) + + Metadata md1 = testBranch.metadata.find {it.key == 'jun1'} + md1.value = 'modtest' + checkAndSave(messageSource, md1) + + + testBranch + + } + + static DataModel modifyAnotherBranch(DataModel anotherBranch, MessageSource messageSource) { + DataClass v1DataClass = anotherBranch.getDataClasses().find {it.label == 'V1 Data Class'} + v1DataClass.description = 'Modified this description for test branch' + v1DataClass.addToMetadata(createdBy: DEVELOPMENT, namespace: 'versioning.com', key: 'mdk1', value: 'mdv1') + checkAndSave(messageSource, v1DataClass) + + DataElement de = v1DataClass.dataElements.find {it.label == 'V1 Second DataElement'} + de.maxMultiplicity = -1 + checkAndSave(messageSource, de) + + PrimitiveType string = new PrimitiveType(createdBy: DEVELOPMENT, label: 'string') + anotherBranch.addToDataTypes(string) + checkAndSave(messageSource, anotherBranch) + + DataClass v2DataClass = anotherBranch.getDataClasses().find {it.label == 'V2 Data Class'} + de = v2DataClass.dataElements.find {it.label == 'V2 Data Element'} + de.dataType = string + checkAndSave(messageSource, de) + + Metadata md1 = anotherBranch.metadata.find {it.key == 'jun1'} + md1.value = 'modanother' + checkAndSave(messageSource, md1) + + anotherBranch + + } + +} \ No newline at end of file diff --git a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelFunctionalSpec.groovy b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelFunctionalSpec.groovy index 2e556cd7e4..24f3ac7400 100644 --- a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelFunctionalSpec.groovy +++ b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/DataModelFunctionalSpec.groovy @@ -146,7 +146,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }''' } @@ -989,8 +989,6 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { ]) verifyResponse CREATED, response def id = response.body().items[0].id - PUT("$id/finalise", [versionChangeType: 'Major']) - verifyResponse OK, response when: long start = System.currentTimeMillis() @@ -1020,8 +1018,6 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { ]) verifyResponse CREATED, response def id = response.body().items[0].id - PUT("$id/finalise", [versionChangeType: 'Major']) - verifyResponse OK, response when: long start = System.currentTimeMillis() @@ -2251,7 +2247,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { /** * In this test we create a DataModel containing one DataClass. The DataModel is finalised, and a new branch 'source' - * created. On the source branch, a DataElement is added to the DataClass. The source branch is then merged + * created. On the source branch, a DataElement is added to the DataClass. The source branch is then merged * back into main, and we check that the DataElement which was created on the source branch is correctly added to the * DataClass on the main branch. */ @@ -2602,7 +2598,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }, "exportMetadata": { @@ -2642,7 +2638,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }, "exportMetadata": { @@ -2791,7 +2787,8 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { "label": "Functional Test Model", "type": "Data Standard", "branchName": "main", - "documentationVersion": "1.0.0" + "documentationVersion": "1.0.0", + "modelVersion": "2.0.0" } ] }''' @@ -2818,7 +2815,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { when: POST('import/uk.ac.ox.softeng.maurodatamapper.datamodel.provider.importer/DataModelJsonImporterService/2.0', [ - finalised : true, + finalised : false, modelName : 'Functional Test Model', folderId : folderId.toString(), importAsNewDocumentationVersion: false, @@ -2847,7 +2844,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { when: POST('import/uk.ac.ox.softeng.maurodatamapper.datamodel.provider.importer/DataModelJsonImporterService/2.0', [ - finalised : true, + finalised : false, modelName : 'Functional Test Model', folderId : folderId.toString(), importAsNewDocumentationVersion: false, @@ -2867,7 +2864,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { when: POST('import/uk.ac.ox.softeng.maurodatamapper.datamodel.provider.importer/DataModelJsonImporterService/2.0', [ - finalised : true, + finalised : false, modelName : 'Functional Test Model', folderId : folderId.toString(), importAsNewDocumentationVersion: false, @@ -3076,7 +3073,6 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { def id = response.body().items[0].id String expected = new String(loadTestFile('simpleDataModel')) .replaceFirst('"exportedBy": "Admin User",', '"exportedBy": "Unlogged User",') - .replace(/Test Authority/, 'Mauro Data Mapper') expect: id @@ -3108,7 +3104,6 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { def id = response.body().items[0].id String expected = new String(loadTestFile('complexDataModel')) .replaceFirst('"exportedBy": "Admin User",', '"exportedBy": "Unlogged User",') - .replace(/Test Authority/, 'Mauro Data Mapper') expect: id @@ -3176,7 +3171,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" }, "id": "${json-unit.matches:id}", "label": "Simple Test DataModel", @@ -3247,7 +3242,7 @@ class DataModelFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" }, "readableByEveryone": false, "readableByAuthenticatedUsers": false, diff --git a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataClassFunctionalSpec.groovy b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataClassFunctionalSpec.groovy index 1fcf5eea92..829c81b346 100644 --- a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataClassFunctionalSpec.groovy +++ b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataClassFunctionalSpec.groovy @@ -91,7 +91,7 @@ class DataClassFunctionalSpec extends OrderedResourceFunctionalSpec { log.debug('Check and setup test data') folder = new Folder(label: 'Functional Test Folder', createdBy: FUNCTIONAL_TEST) checkAndSave(folder) - Authority testAuthority = new Authority(label: 'Test Authority', url: "https://localhost", createdBy: FUNCTIONAL_TEST) + Authority testAuthority = Authority.findByLabel('Test Authority') checkAndSave(testAuthority) DataModel dataModel = new DataModel(label: 'Functional Test DataModel', createdBy: FUNCTIONAL_TEST, @@ -120,7 +120,6 @@ class DataClassFunctionalSpec extends OrderedResourceFunctionalSpec { def cleanupSpec() { log.debug('CleanupSpec DataClassFunctionalSpec') cleanUpResources(DataType, DataModel, Folder) - Authority.findByLabel('Test Authority').delete(flush: true) } @Override diff --git a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataElementFunctionalSpec.groovy b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataElementFunctionalSpec.groovy index b9278d85b4..790f18decf 100644 --- a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataElementFunctionalSpec.groovy +++ b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/DataElementFunctionalSpec.groovy @@ -103,7 +103,7 @@ class DataElementFunctionalSpec extends OrderedResourceFunctionalSpec { assert DataModel.count() == 0 folder = new Folder(label: 'Functional Test Folder', createdBy: FUNCTIONAL_TEST) checkAndSave(folder) - Authority testAuthority = new Authority(label: 'Test Authority', url: "https://localhost", createdBy: FUNCTIONAL_TEST) + Authority testAuthority = Authority.findByLabel('Test Authority') checkAndSave(testAuthority) DataModel dataModel = new DataModel(label: 'Functional Test DataModel', createdBy: FUNCTIONAL_TEST, folder: folder, authority: testAuthority).save(flush: true) @@ -89,7 +89,6 @@ class NestedDataClassFunctionalSpec extends ResourceFunctionalSpec { def cleanupSpec() { log.debug('CleanupSpec NestedDataClassFunctionalSpec') cleanUpResources(DataType, DataModel, Folder) - Authority.findByLabel('Test Authority').delete(flush: true) } @Override diff --git a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/DataTypeFunctionalSpec.groovy b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/DataTypeFunctionalSpec.groovy index c684e1a01b..35c402f137 100644 --- a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/DataTypeFunctionalSpec.groovy +++ b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/DataTypeFunctionalSpec.groovy @@ -79,7 +79,7 @@ class DataTypeFunctionalSpec extends OrderedResourceFunctionalSpec { log.debug('Check and setup test data') folder = new Folder(label: 'Functional Test Folder', createdBy: FUNCTIONAL_TEST) checkAndSave(folder) - Authority testAuthority = new Authority(label: 'Test Authority', url: "https://localhost", createdBy: FUNCTIONAL_TEST) + Authority testAuthority = Authority.findByLabel('Test Authority') checkAndSave(testAuthority) DataModel dataModel = new DataModel(label: 'Functional Test DataModel', createdBy: FUNCTIONAL_TEST, folder: folder, authority: testAuthority).save(flush: true) @@ -104,7 +104,6 @@ class DataTypeFunctionalSpec extends OrderedResourceFunctionalSpec { def cleanupSpec() { log.debug('CleanupSpec DataTypeFunctionalSpec') cleanUpResources(DataClass, DataModel, Folder) - Authority.findByLabel('Test Authority').delete(flush: true) } @Override diff --git a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/enumeration/EnumerationValueFunctionalSpec.groovy b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/enumeration/EnumerationValueFunctionalSpec.groovy index b38fdd8ef4..f6c20fe779 100644 --- a/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/enumeration/EnumerationValueFunctionalSpec.groovy +++ b/mdm-plugin-datamodel/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/datamodel/item/datatype/enumeration/EnumerationValueFunctionalSpec.groovy @@ -69,7 +69,7 @@ class EnumerationValueFunctionalSpec extends ResourceFunctionalSpec *' + mdmCore : '4.0.0 > *', + assetPipeline: '3.0.11 > *', ] Closure doWithSpring() { diff --git a/mdm-plugin-email-proxy/grails-app/conf/application.yml b/mdm-plugin-email-proxy/grails-app/conf/application.yml index f2f78b0d39..0726b39c8d 100644 --- a/mdm-plugin-email-proxy/grails-app/conf/application.yml +++ b/mdm-plugin-email-proxy/grails-app/conf/application.yml @@ -123,7 +123,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -133,6 +133,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: diff --git a/mdm-plugin-federation/grails-app/conf/application.yml b/mdm-plugin-federation/grails-app/conf/application.yml index ec12612f9f..a634622f20 100644 --- a/mdm-plugin-federation/grails-app/conf/application.yml +++ b/mdm-plugin-federation/grails-app/conf/application.yml @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: diff --git a/mdm-plugin-federation/grails-app/conf/db/migration/federation/V1_1_0__allow_public_connections.sql b/mdm-plugin-federation/grails-app/conf/db/migration/federation/V1_1_0__allow_public_connections.sql new file mode 100644 index 0000000000..1bf836a2b3 --- /dev/null +++ b/mdm-plugin-federation/grails-app/conf/db/migration/federation/V1_1_0__allow_public_connections.sql @@ -0,0 +1,2 @@ +ALTER TABLE federation.subscribed_catalogue + ALTER COLUMN api_key DROP NOT NULL; \ No newline at end of file diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueController.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueController.groovy index 51ac75e8fe..855fe3e292 100644 --- a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueController.groovy +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueController.groovy @@ -53,34 +53,34 @@ class SubscribedCatalogueController extends EditLoggingController listAllReadableResources(Map params) { - subscribedCatalogueService.list() + subscribedCatalogueService.list(params) } } diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelController.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelController.groovy index fd19343509..0b3b85fcba 100644 --- a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelController.groovy +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelController.groovy @@ -17,26 +17,17 @@ */ package uk.ac.ox.softeng.maurodatamapper.federation -import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiInternalException + import uk.ac.ox.softeng.maurodatamapper.core.container.Folder import uk.ac.ox.softeng.maurodatamapper.core.container.FolderService import uk.ac.ox.softeng.maurodatamapper.core.controller.EditLoggingController -import uk.ac.ox.softeng.maurodatamapper.core.model.Model import uk.ac.ox.softeng.maurodatamapper.core.model.ModelService -import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.ModelImporterProviderService -import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.parameter.FileParameter -import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.parameter.ModelImporterProviderServiceParameters import uk.ac.ox.softeng.maurodatamapper.security.SecurityPolicyManagerService import grails.gorm.transactions.Transactional import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired - -import java.time.OffsetDateTime - -import static org.springframework.http.HttpStatus.NO_CONTENT -import static org.springframework.http.HttpStatus.OK -import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY +import org.springframework.validation.Errors @Slf4j class SubscribedModelController extends EditLoggingController { @@ -58,9 +49,29 @@ class SubscribedModelController extends EditLoggingController { super(SubscribedModel) } + @Transactional @Override - void serviceDeleteResource(SubscribedModel resource) { - subscribedModelService.delete(resource) + def save() { + if (handleReadOnly()) return + + def instance = createResource() + + if (response.isCommitted()) return + + if (!validateResource(instance, 'create')) return + + if (params.boolean('federate', true)) { + def federationResult = subscribedModelService.federateSubscribedModel(instance, currentUserSecurityPolicyManager) + if (federationResult instanceof Errors) { + transactionStatus.setRollbackOnly() + respond federationResult, view: 'create' // STATUS CODE 422 + return + } + } + + saveResource instance + + saveResponse instance } /** @@ -88,85 +99,20 @@ class SubscribedModelController extends EditLoggingController { return forbiddenDueToPermissions() } - Folder folder = folderService.get(subscribedModel.folderId) - - //Export the requested model from the SubscribedCatalogue - log.debug("Exporting SubscribedModel ${params.subscribedModelId} as Json") - String exportedJson = - subscribedModelService.exportSubscribedModelFromSubscribedCatalogue(subscribedModelService.get(params.subscribedModelId)) - if (!exportedJson) { - log.debug("No Json exported") - request.withFormat { - '*' {render status: NO_CONTENT} // NO CONTENT STATUS CODE - } - return - } - - log.debug("exportedJson {}", exportedJson) - - - //Get a ModelService to handle the domain type we are dealing with - ModelService modelService = modelServices.find {it.handles(subscribedModel.subscribedModelType)} - - ModelImporterProviderService modelImporterProviderService = modelService.getJsonModelImporterProviderService() - - //Import the model - ModelImporterProviderServiceParameters parameters = - modelImporterProviderService.createNewImporterProviderServiceParameters() as ModelImporterProviderServiceParameters - - if (parameters.hasProperty('importFile')?.type != FileParameter) { - throw new ApiInternalException('MSXX', "Assigned JSON importer ${modelImporterProviderService.class.simpleName} " + - "for model cannot import file content") - } - - parameters.importFile = new FileParameter(fileContents: exportedJson.getBytes()) - parameters.folderId = folder.id - parameters.finalised = true - parameters.useDefaultAuthority = false - - Model model = modelImporterProviderService.importDomain(currentUser, parameters) - - if (!model) { - transactionStatus.setRollbackOnly() - return errorResponse(UNPROCESSABLE_ENTITY, 'No model imported') - } - - model.folder = folder - - modelService.validate(model) - - if (model.hasErrors()) { + def federationResult = subscribedModelService.federateSubscribedModel(subscribedModel, currentUserSecurityPolicyManager) + if (federationResult instanceof Errors) { transactionStatus.setRollbackOnly() - respond model.errors - return - } - - log.debug('No errors in imported model') - - Model savedModel = modelService.saveModelWithContent(model) - log.debug('Saved model') - if (securityPolicyManagerService) { - log.debug("add security to saved model") - currentUserSecurityPolicyManager = securityPolicyManagerService.addSecurityForSecurableResource(savedModel, currentUser, savedModel.label) + respond federationResult, view: 'update' // STATUS CODE 422 } + updateResource(subscribedModel) + updateResponse(subscribedModel) - //Record the ID of the imported model against the subscription makes it easier to track version links later. - subscribedModel.lastRead = OffsetDateTime.now() - subscribedModel.localModelId = savedModel.id - subscribedModelService.save(subscribedModel) - log.info('Single Model Import complete') - - - //Handle version linking - Map versionLinks = subscribedModelService.getVersionLinks(modelService.getUrlResourceName(), subscribedModel) - if (versionLinks) { - log.debug("add version links") - subscribedModelService.addVersionLinksToImportedModel(currentUser, versionLinks, modelService, subscribedModel) - } + } - //Respond with the subscribed model - respond subscribedModel, status: OK, view: 'show' + @Override + void serviceDeleteResource(SubscribedModel resource) { + subscribedModelService.delete(resource) } @Override @@ -175,7 +121,7 @@ class SubscribedModelController extends EditLoggingController { SubscribedModel resource = super.createResource() as SubscribedModel //Create an association between the SubscribedCatalogue and SubscribedModel - subscribedCatalogueService.get(params.subscribedCatalogueId)?.addToSubscribedModels(resource) + resource.subscribedCatalogue = subscribedCatalogueService.get(params.subscribedCatalogueId) resource } @@ -184,21 +130,47 @@ class SubscribedModelController extends EditLoggingController { protected SubscribedModel saveResource(SubscribedModel resource) { SubscribedModel subscribedModel = super.saveResource(resource) as SubscribedModel if (securityPolicyManagerService) { - currentUserSecurityPolicyManager = securityPolicyManagerService.addSecurityForSecurableResource(subscribedModel, - currentUser, - subscribedModel.subscribedModelId. - toString()) + currentUserSecurityPolicyManager = securityPolicyManagerService.addSecurityForSecurableResource( + subscribedModel, + currentUser, + subscribedModel.subscribedModelId.toString()) } subscribedModel } + @Override + @Transactional + protected boolean validateResource(SubscribedModel instance, String view) { + // Make sure any existing errors are returned first..such as parse errors + if (instance.hasErrors()) { + transactionStatus.setRollbackOnly() + respond instance.errors, view: view // STATUS CODE 422 + return false + } + + instance.validate() + + //Check we can import into the requested folder, and get the folder + if (instance.folderId && !currentUserSecurityPolicyManager.userCanEditSecuredResourceId(Folder, instance.folderId)) { + instance.errors.rejectValue('folderId', 'invalid.subscribedmodel.folderid.no.permissions', + 'Invalid folderId for subscribed model, user does not have the necessary permissions') + } + + if (instance.hasErrors()) { + transactionStatus.setRollbackOnly() + respond instance.errors, view: view // STATUS CODE 422 + return false + } + true + } + @Override protected SubscribedModel queryForResource(Serializable id) { - subscribedModelService.get(id) + subscribedModelService.findBySubscribedCatalogueIdAndId(params.subscribedCatalogueId, id) } @Override protected List listAllReadableResources(Map params) { - subscribedModelService.list() + subscribedModelService.findAllBySubscribedCatalogueId(params.subscribedCatalogueId, params) } } diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelInterceptor.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelInterceptor.groovy index 020e912fa8..a5abc5824e 100644 --- a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelInterceptor.groovy +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelInterceptor.groovy @@ -33,12 +33,13 @@ class SubscribedModelInterceptor extends SecurableResourceInterceptor { @Override void checkIds() { Utils.toUuid(params, 'id') + Utils.toUuid(params, 'subscribedModelId') Utils.toUuid(params, 'subscribedCatalogueId') } @Override UUID getId() { - params.id + params.subscribedModelId ?: params.id } boolean before() { diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/UrlMappings.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/UrlMappings.groovy index 6fb8e50e8d..207540088e 100644 --- a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/UrlMappings.groovy +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/UrlMappings.groovy @@ -29,10 +29,13 @@ class UrlMappings { get "/feeds/all"(controller: 'feed', action: 'index') '/subscribedCatalogues'(resources: 'subscribedCatalogue') { - get '/availableModels'(controller: 'subscribedCatalogue', action: 'availableModels') + get '/availableModels'(controller: 'subscribedCatalogue', action: 'publishedModels') // to be removed + get '/publishedModels'(controller: 'subscribedCatalogue', action: 'publishedModels') '/subscribedModels'(resources: 'subscribedModel', excludes: DEFAULT_EXCLUDES) } post "/subscribedModels/$subscribedModelId/federate"(controller: 'subscribedModel', action: 'federate') + + get '/published/models'(controller: 'publish', action: 'index') } } } diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/FeedController.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedController.groovy similarity index 96% rename from mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/FeedController.groovy rename to mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedController.groovy index 3067a347b1..1176a82cd0 100644 --- a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/FeedController.groovy +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedController.groovy @@ -15,7 +15,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package uk.ac.ox.softeng.maurodatamapper.federation +package uk.ac.ox.softeng.maurodatamapper.federation.atom import uk.ac.ox.softeng.maurodatamapper.core.model.Model diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/FeedInterceptor.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedInterceptor.groovy similarity index 94% rename from mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/FeedInterceptor.groovy rename to mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedInterceptor.groovy index 31a2a011bb..20f58c857e 100644 --- a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/FeedInterceptor.groovy +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedInterceptor.groovy @@ -15,7 +15,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package uk.ac.ox.softeng.maurodatamapper.federation +package uk.ac.ox.softeng.maurodatamapper.federation.atom import uk.ac.ox.softeng.maurodatamapper.core.traits.controller.MdmInterceptor diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishController.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishController.groovy new file mode 100644 index 0000000000..7a2070632b --- /dev/null +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishController.groovy @@ -0,0 +1,58 @@ +/* + * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package uk.ac.ox.softeng.maurodatamapper.federation.publish + +import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority +import uk.ac.ox.softeng.maurodatamapper.core.authority.AuthorityService +import uk.ac.ox.softeng.maurodatamapper.core.model.Model +import uk.ac.ox.softeng.maurodatamapper.core.traits.controller.ResourcelessMdmController +import uk.ac.ox.softeng.maurodatamapper.federation.PublishedModel +import uk.ac.ox.softeng.maurodatamapper.federation.publish.PublishService + +import grails.rest.RestfulController + +/** + * Produce an ATOM feed of all Models. Only respond in ATOM format. To render the response in ATOM, + * beans need to be configured in uk.ac.ox.softeng.maurodatamapper.core.MdmCoreGrailsPlugin, like this: + * + * import uk.ac.ox.softeng.maurodatamapper.core.model.Model + * import uk.ac.ox.softeng.maurodatamapper.federation.rest.render.MdmAtomModelCollectionRenderer + * beans = {* halModelListRenderer(MdmAtomModelCollectionRenderer, Collection) {* includes = [] + *}* halModelRenderer(MdmAtomModelCollectionRenderer, Model) {* includes = [] + *}*}* + * @since 04/01/2021 + */ +class PublishController extends RestfulController implements ResourcelessMdmController { + + static responseFormats = ['json', 'xml'] + + PublishService publishService + + AuthorityService authorityService + + PublishController() { + super(Model) + } + + def index() { + List publishedModels = publishService.findAllPublishedReadableModels(currentUserSecurityPolicyManager) + Authority authority = authorityService.defaultAuthority + respond(publishedModels: publishedModels, authority: authority) + } + +} diff --git a/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishInterceptor.groovy b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishInterceptor.groovy new file mode 100644 index 0000000000..9049d4b98a --- /dev/null +++ b/mdm-plugin-federation/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishInterceptor.groovy @@ -0,0 +1,29 @@ +/* + * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package uk.ac.ox.softeng.maurodatamapper.federation.publish + +import uk.ac.ox.softeng.maurodatamapper.core.traits.controller.MdmInterceptor + +class PublishInterceptor implements MdmInterceptor { + + boolean before() { + // Allow anyone to retrieve feeds + actionName == 'index' + } + +} diff --git a/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogue.groovy b/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogue.groovy index 80ff03ec76..53085f07ca 100644 --- a/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogue.groovy +++ b/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogue.groovy @@ -56,6 +56,7 @@ class SubscribedCatalogue implements SecurableResource, EditHistoryAware, Inform label unique: true refreshPeriod nullable: true lastRead nullable: true + apiKey nullable: true } static mapping = { diff --git a/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModel.groovy b/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModel.groovy index 74a34a8092..a19960d5fc 100644 --- a/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModel.groovy +++ b/mdm-plugin-federation/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModel.groovy @@ -17,43 +17,36 @@ */ package uk.ac.ox.softeng.maurodatamapper.federation - import uk.ac.ox.softeng.maurodatamapper.core.traits.domain.EditHistoryAware import uk.ac.ox.softeng.maurodatamapper.security.SecurableResource -import uk.ac.ox.softeng.maurodatamapper.traits.domain.CreatorAware import grails.gorm.DetachedCriteria import java.time.OffsetDateTime -class SubscribedModel implements CreatorAware, SecurableResource, EditHistoryAware { +class SubscribedModel implements SecurableResource, EditHistoryAware { UUID id //The ID of the model on the remote (subscribed) catalogue UUID subscribedModelId - - String subscribedModelType - + String subscribedModelType //The folder that the model should be imported into UUID folderId - //The last time that the model was last read from the remote (subscribed) catalogue OffsetDateTime lastRead - //The ID of the model when imported into the local catalogue. UUID localModelId - - Boolean readableByEveryone Boolean readableByAuthenticatedUsers - + static belongsTo = [subscribedCatalogue: SubscribedCatalogue] static constraints = { subscribedCatalogue nullable: false folderId nullable: false - subscribedModelId nullable: false + subscribedModelId nullable: false, unique: 'subscribedCatalogue' // Should prevent subscribing to the same modelId from same catalogue + subscribedModelType blank: false lastRead nullable: true localModelId nullable: true } @@ -70,25 +63,31 @@ class SubscribedModel implements CreatorAware, SecurableResource, EditHistoryAw @Override String getDomainType() { SubscribedModel.simpleName - } + } @Override String getEditLabel() { "SubscribedModel:${id}" - } + } static DetachedCriteria by() { new DetachedCriteria(SubscribedModel) } + static DetachedCriteria bySubscribedCatalogueId(UUID subscribedCatalogueId) { + by().eq('subscribedCatalogue.id', subscribedCatalogueId) + } + static DetachedCriteria bySubscribedCatalogueIdAndSubscribedModelId(UUID subscribedCatalogueId, UUID subscribedModelId) { - by() - .eq('subscribedCatalogue.id', subscribedCatalogueId) - .eq('subscribedModelId', subscribedModelId) + bySubscribedCatalogueId(subscribedCatalogueId).eq('subscribedModelId', subscribedModelId) + } + + static DetachedCriteria bySubscribedCatalogueIdAndId(UUID subscribedCatalogueId, UUID id) { + bySubscribedCatalogueId(subscribedCatalogueId).idEq(id) } static DetachedCriteria bySubscribedModelId(UUID subscribedModelId) { by() - .eq('subscribedModelId', subscribedModelId) + .eq('subscribedModelId', subscribedModelId) } } diff --git a/mdm-plugin-federation/grails-app/i18n/messages.properties b/mdm-plugin-federation/grails-app/i18n/messages.properties index f0be5375e9..e97a76a1e9 100644 --- a/mdm-plugin-federation/grails-app/i18n/messages.properties +++ b/mdm-plugin-federation/grails-app/i18n/messages.properties @@ -16,5 +16,11 @@ # SPDX-License-Identifier: Apache-2.0 # invalid.subscription.url.apikey=Invalid subscription to catalogue at [{0}] cannot connect using provided Api Key +invalid.subscription.url.authority=Invalid subscription to catalogue at [{0}] as it has the same Authority as this instance [{1}:{2}] # Used as title attribute in atom feed -resource.api.feeds.all.href.title=Mauro Data Mapper - All Models \ No newline at end of file +resource.api.feeds.all.href.title=Mauro Data Mapper - All Models +invalid.subscribedmodel.folderid.no.permissions=Invalid folderId for subscribed model, user does not have the necessary permissions +invalid.subscribedmodel.export=Could not export SubscribedModel from SubscribedCatalogue +invalid.subscribedmodel.import=Could not import SubscribedModel into local Catalogue +invalid.subscribedmodel.federate.exception=Could not federate SubscribedModel into local Catalogue due to [{0}] +invalid.subscribedmodel.import.already.exists=Model from authority [{0}] with label [{1}] and version [{2}] already exists in catalogue \ No newline at end of file diff --git a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueService.groovy b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueService.groovy index 3d0e925a02..9e231d5585 100644 --- a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueService.groovy +++ b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedCatalogueService.groovy @@ -17,20 +17,22 @@ */ package uk.ac.ox.softeng.maurodatamapper.federation - +import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority +import uk.ac.ox.softeng.maurodatamapper.core.authority.AuthorityService import uk.ac.ox.softeng.maurodatamapper.core.traits.provider.importer.XmlImportMapping import uk.ac.ox.softeng.maurodatamapper.federation.web.FederationClient import uk.ac.ox.softeng.maurodatamapper.util.Utils import grails.gorm.transactions.Transactional +import grails.util.Environment import groovy.util.logging.Slf4j -import groovy.util.slurpersupport.GPathResult import io.micronaut.http.client.HttpClientConfiguration import io.micronaut.http.client.ssl.NettyClientSslBuilder import io.micronaut.http.codec.MediaTypeCodecRegistry import org.springframework.beans.factory.annotation.Autowired import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter @Transactional @Slf4j @@ -43,6 +45,8 @@ class SubscribedCatalogueService implements XmlImportMapping { @Autowired MediaTypeCodecRegistry mediaTypeCodecRegistry + AuthorityService authorityService + SubscribedCatalogue get(Serializable id) { SubscribedCatalogue.get(id) } @@ -63,41 +67,67 @@ class SubscribedCatalogueService implements XmlImportMapping { subscribedCatalogue.save(failOnError: true, validate: false) } - boolean verifyConnectionToSubscribedCatalogue(SubscribedCatalogue subscribedCatalogue) { - FederationClient client = getFederationClientForSubscribedCatalogue(subscribedCatalogue) - client.isConnectionPossible(subscribedCatalogue.apiKey) + void verifyConnectionToSubscribedCatalogue(SubscribedCatalogue subscribedCatalogue) { + try { + FederationClient client = getFederationClientForSubscribedCatalogue(subscribedCatalogue) + Map catalogueModels = client.getSubscribedCatalogueModels(subscribedCatalogue.apiKey) + if (!catalogueModels) { + subscribedCatalogue.errors.reject('invalid.subscription.url.apikey', + [subscribedCatalogue.url].toArray(), + 'Invalid subscription to catalogue at [{0}] cannot connect using provided Api Key') + } + Authority thisAuthority = authorityService.defaultAuthority + + // Under prod mode dont let a connection to ourselves exist + if (Environment.current == Environment.PRODUCTION) { + if (catalogueModels.authority.label == thisAuthority.label && catalogueModels.authority.url == thisAuthority.url) { + subscribedCatalogue.errors.reject( + 'invalid.subscription.url.authority', + [subscribedCatalogue.url, + catalogueModels.authority.label, + catalogueModels.authority.url].toArray(), + 'Invalid subscription to catalogue at [{0}] as it has the same Authority as this instance [{1}:{2}]') + } + } + } catch (Exception exception) { + subscribedCatalogue.errors.reject('invalid.subscription.url.apikey', + [subscribedCatalogue.url].toArray(), + 'Invalid subscription to catalogue at [{0}] cannot connect using provided Api Key') + log.warn('Unable to confirm catalogue subscription due to exception', exception) + } + subscribedCatalogue } /** - * Return a list of models available on the subscribed catalogue. - * 1. Connect to the endpoint /api/feeds/all on the remote, authenticating by setting an api key in the header + * Return a list of models available on the subscribed catalogue. + * 1. Connect to the endpoint /api/published/models on the remote, authenticating by setting an api key in the header * 2. Parse the returned Atom feed, picking out nodes * 3. For each , create an AvailableModel * 4. Return the list of AvailableModel, in order that this can be rendered as json * * @param subscribedCatalogue The catalogue we want to query - * @return List The list of available models returned by the catalogue + * @return List The list of available models returned by the catalogue * */ - List listAvailableModels(SubscribedCatalogue subscribedCatalogue) { + List listPublishedModels(SubscribedCatalogue subscribedCatalogue) { FederationClient client = getFederationClientForSubscribedCatalogue(subscribedCatalogue) - GPathResult feedData = client.getSubscribedCatalogueModels(subscribedCatalogue.apiKey) - - //Iterate the nodes, making an AvailableModel for each one - def entries = feedData.entry - - if (entries.isEmpty()) return [] - - entries.collect {entry -> - new AvailableModel( - id: Utils.toUuid(extractUuidFromUrn(entry.id.text())), - label: entry.title.text(), - description: entry.summary.text(), - modelType: entry.category.@term, - lastUpdated: OffsetDateTime.parse(entry.updated.text()) - ) + Map subscribedCatalogueModels = client.getSubscribedCatalogueModels(subscribedCatalogue.apiKey) + + if (subscribedCatalogueModels.publishedModels.isEmpty()) return [] + + (subscribedCatalogueModels.publishedModels as List>).collect { pm -> + new PublishedModel().tap { + modelId = Utils.toUuid(pm.id) + title = pm.title + modelType = pm.modelType + lastUpdated = OffsetDateTime.parse(pm.lastUpdated, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + dateCreated = OffsetDateTime.parse(pm.dateCreated, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + datePublished = OffsetDateTime.parse(pm.datePublished, DateTimeFormatter.ISO_OFFSET_DATE_TIME) + author = pm.author + description = pm.description + } } } diff --git a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelService.groovy b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelService.groovy index 00035f33dd..b63d12b83e 100644 --- a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelService.groovy +++ b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelService.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital + * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,26 @@ package uk.ac.ox.softeng.maurodatamapper.federation import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiBadRequestException +import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiException +import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiInternalException +import uk.ac.ox.softeng.maurodatamapper.core.container.Folder +import uk.ac.ox.softeng.maurodatamapper.core.container.FolderService import uk.ac.ox.softeng.maurodatamapper.core.facet.VersionLinkType import uk.ac.ox.softeng.maurodatamapper.core.model.Model import uk.ac.ox.softeng.maurodatamapper.core.model.ModelService +import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.ModelImporterProviderService +import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.parameter.FileParameter +import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.parameter.ModelImporterProviderServiceParameters +import uk.ac.ox.softeng.maurodatamapper.security.SecurityPolicyManagerService import uk.ac.ox.softeng.maurodatamapper.security.User +import uk.ac.ox.softeng.maurodatamapper.security.UserSecurityPolicyManager import grails.gorm.transactions.Transactional import groovy.util.logging.Slf4j import org.springframework.beans.factory.annotation.Autowired +import java.time.OffsetDateTime + @Slf4j @Transactional class SubscribedModelService { @@ -35,6 +46,10 @@ class SubscribedModelService { List modelServices SubscribedCatalogueService subscribedCatalogueService + FolderService folderService + + @Autowired(required = false) + SecurityPolicyManagerService securityPolicyManagerService SubscribedModel get(Serializable id) { SubscribedModel.get(id) @@ -44,12 +59,21 @@ class SubscribedModelService { SubscribedModel.bySubscribedCatalogueIdAndSubscribedModelId(subscribedCatalogueId, subscribedModelId).get() } + + SubscribedModel findBySubscribedCatalogueIdAndId(UUID subscribedCatalogueId, UUID id) { + SubscribedModel.bySubscribedCatalogueIdAndId(subscribedCatalogueId, id).get() + } + SubscribedModel findBySubscribedModelId(UUID subscribedModelId) { SubscribedModel.bySubscribedModelId(subscribedModelId).get() } - List list(Map pagination) { - pagination ? SubscribedModel.list(pagination) : SubscribedModel.list() + List list(Map pagination = [:]) { + SubscribedModel.list(pagination) + } + + List findAllBySubscribedCatalogueId(UUID subscribedCatalogueId, Map pagination = [:]) { + SubscribedModel.bySubscribedCatalogueId(subscribedCatalogueId).list(pagination) } Long count() { @@ -64,6 +88,99 @@ class SubscribedModelService { subscribedModel.save(failOnError: true, validate: false) } + def federateSubscribedModel(SubscribedModel subscribedModel, UserSecurityPolicyManager userSecurityPolicyManager) { + + Folder folder = folderService.get(subscribedModel.folderId) + + //Export the requested model from the SubscribedCatalogue + log.debug("Exporting SubscribedModel") + try { + String exportedJson = exportSubscribedModelFromSubscribedCatalogue(subscribedModel) + + if (!exportedJson) { + log.debug("No Json exported") + subscribedModel.errors.reject('invalid.subscribedmodel.export', + 'Could not export SubscribedModel from SubscribedCatalogue') + return subscribedModel.errors + } + + //Get a ModelService to handle the domain type we are dealing with + ModelService modelService = modelServices.find {it.handles(subscribedModel.subscribedModelType)} + ModelImporterProviderService modelImporterProviderService = modelService.getJsonModelImporterProviderService() + + //Import the model + ModelImporterProviderServiceParameters parameters = + modelImporterProviderService.createNewImporterProviderServiceParameters() as ModelImporterProviderServiceParameters + + if (parameters.hasProperty('importFile')?.type != FileParameter) { + throw new ApiInternalException('MSXX', "Assigned JSON importer ${modelImporterProviderService.class.simpleName} " + + "for model cannot import file content") + } + + parameters.importFile = new FileParameter(fileContents: exportedJson.getBytes()) + parameters.folderId = folder.id + parameters.finalised = true + parameters.useDefaultAuthority = false + + Model model = modelImporterProviderService.importDomain(userSecurityPolicyManager.user, parameters) + + if (!model) { + subscribedModel.errors.reject('invalid.subscribedmodel.import', + 'Could not import SubscribedModel into local Catalogue') + return subscribedModel.errors + } + + if (modelService.countByAuthorityAndLabelAndVersion(model.authority, model.label, model.modelVersion)) { + subscribedModel.errors.reject('invalid.subscribedmodel.import.already.exists', + [model.authority, model.label, model.modelVersion].toArray(), + 'Model from authority [{0}] with label [{1}] and version [{2}] already exists in catalogue') + return subscribedModel.errors + } + + + model.folder = folder + + modelService.validate(model) + + if (model.hasErrors()) { + return model.errors + } + + log.debug('No errors in imported model') + + Model savedModel = modelService.saveModelWithContent(model) + log.debug('Saved model') + + if (userSecurityPolicyManager) { + log.debug("add security to saved model") + userSecurityPolicyManager = securityPolicyManagerService.addSecurityForSecurableResource(savedModel, + userSecurityPolicyManager.user, + savedModel.label) + } + + + //Record the ID of the imported model against the subscription makes it easier to track version links later. + subscribedModel.lastRead = OffsetDateTime.now() + subscribedModel.localModelId = savedModel.id + + //Handle version linking + Map versionLinks = getVersionLinks(modelService.getUrlResourceName(), subscribedModel) + if (versionLinks) { + log.debug("add version links") + addVersionLinksToImportedModel(userSecurityPolicyManager.user, versionLinks, modelService, subscribedModel) + } + + } catch (ApiException exception) { + log.warn("Failed to federate subscribedModel due to [${exception.message}]") + subscribedModel.errors.reject('invalid.subscribedmodel.federate.exception', + [exception.message].toArray(), + 'Could not federate SubscribedModel into local Catalogue due to [{0}]') + return subscribedModel.errors + } + + subscribedModel + } + /** * Get version links from the subscribed catalogue for the specified subscribed model * 1. Create a URL for {modelDomainType}/{modelId}/versionLinks @@ -168,4 +285,5 @@ class SubscribedModelService { } exporterMap } + } diff --git a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedService.groovy b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedService.groovy new file mode 100644 index 0000000000..8fddf7cd55 --- /dev/null +++ b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/atom/FeedService.groovy @@ -0,0 +1,43 @@ +/* + * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package uk.ac.ox.softeng.maurodatamapper.federation.atom + +import uk.ac.ox.softeng.maurodatamapper.core.authority.AuthorityService +import uk.ac.ox.softeng.maurodatamapper.core.model.Model +import uk.ac.ox.softeng.maurodatamapper.core.model.ModelService +import uk.ac.ox.softeng.maurodatamapper.federation.publish.PublishService +import uk.ac.ox.softeng.maurodatamapper.security.UserSecurityPolicyManager + +import grails.gorm.transactions.Transactional +import groovy.util.logging.Slf4j +import org.springframework.beans.factory.annotation.Autowired + +@Transactional +@Slf4j +class FeedService { + + PublishService publishService + AuthorityService authorityService + + @Autowired(required = false) + List modelServices + + List findModels(UserSecurityPolicyManager userSecurityPolicyManager) { + publishService.findAllReadableModelsToPublish(userSecurityPolicyManager) + } +} diff --git a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/FeedService.groovy b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishService.groovy similarity index 78% rename from mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/FeedService.groovy rename to mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishService.groovy index 862089625c..e8e6689d62 100644 --- a/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/FeedService.groovy +++ b/mdm-plugin-federation/grails-app/services/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishService.groovy @@ -15,11 +15,12 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package uk.ac.ox.softeng.maurodatamapper.federation +package uk.ac.ox.softeng.maurodatamapper.federation.publish import uk.ac.ox.softeng.maurodatamapper.core.authority.AuthorityService import uk.ac.ox.softeng.maurodatamapper.core.model.Model import uk.ac.ox.softeng.maurodatamapper.core.model.ModelService +import uk.ac.ox.softeng.maurodatamapper.federation.PublishedModel import uk.ac.ox.softeng.maurodatamapper.security.UserSecurityPolicyManager import grails.gorm.transactions.Transactional @@ -28,27 +29,27 @@ import org.springframework.beans.factory.annotation.Autowired @Transactional @Slf4j -class FeedService { +class PublishService { AuthorityService authorityService @Autowired(required = false) List modelServices - List findModels(UserSecurityPolicyManager userSecurityPolicyManager) { - + List findAllReadableModelsToPublish(UserSecurityPolicyManager userSecurityPolicyManager) { List models = [] - modelServices.each { List readableModels = it.findAllReadableModels(userSecurityPolicyManager, false, true, false) // Only publish finalised models which belong to this instance of MDM - List publishableModels = readableModels.findAll {Model model -> + List publishableModels = readableModels.findAll { Model model -> model.finalised && model.authority.id == authorityService.getDefaultAuthority().id } as List models.addAll(publishableModels) } - models } + List findAllPublishedReadableModels(UserSecurityPolicyManager userSecurityPolicyManager) { + findAllReadableModelsToPublish(userSecurityPolicyManager).collect { new PublishedModel(it) } + } } diff --git a/mdm-plugin-federation/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/federation/rest/render/MdmAtomModelRenderer.groovy b/mdm-plugin-federation/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/federation/rest/render/MdmAtomModelRenderer.groovy index 82ec778153..d0af391172 100644 --- a/mdm-plugin-federation/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/federation/rest/render/MdmAtomModelRenderer.groovy +++ b/mdm-plugin-federation/grails-app/utils/uk/ac/ox/softeng/maurodatamapper/federation/rest/render/MdmAtomModelRenderer.groovy @@ -17,7 +17,6 @@ */ package uk.ac.ox.softeng.maurodatamapper.federation.rest.render - import uk.ac.ox.softeng.maurodatamapper.core.admin.ApiPropertyService import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority import uk.ac.ox.softeng.maurodatamapper.core.authority.AuthorityService @@ -30,7 +29,6 @@ import grails.rest.render.atom.AtomRenderer import groovy.util.logging.Slf4j import org.grails.datastore.mapping.model.PersistentEntity import org.grails.datastore.mapping.model.types.ToOne -import org.grails.web.xml.PrettyPrintXMLStreamWriter import org.grails.web.xml.StreamingMarkupWriter import org.grails.web.xml.XMLStreamWriter import org.springframework.beans.factory.annotation.Autowired @@ -78,8 +76,7 @@ class MdmAtomModelRenderer extends AtomRenderer { @Override void renderInternal(T object, RenderContext context) { final streamingWriter = new StreamingMarkupWriter(context.writer, encoding) - XMLStreamWriter w = prettyPrint ? new PrettyPrintXMLStreamWriter(streamingWriter) : new XMLStreamWriter(streamingWriter) - XML xml = new XML(w) + XML xml = new XML(new XMLStreamWriter(streamingWriter)) final entity = mappingContext.getPersistentEntity(object.class.name) boolean isDomain = entity != null @@ -87,7 +84,8 @@ class MdmAtomModelRenderer extends AtomRenderer { Authority authority = authorityService.defaultAuthority Set writtenObjects = [] - w.startDocument(encoding, "1.0") + XMLStreamWriter writer = xml.getWriter() + writer.startDocument(encoding, "1.0") if (isDomain) { writeDomainWithEmbeddedAndLinks(entity, object, context, xml, writtenObjects) @@ -95,7 +93,6 @@ class MdmAtomModelRenderer extends AtomRenderer { final locale = context.locale String resourceHref = linkGenerator.link(uri: context.resourcePath, method: HttpMethod.GET, absolute: true) final title = getResourceTitle(context.resourcePath, locale) - XMLStreamWriter writer = xml.getWriter() writer .startNode(FEED_TAG) .attribute(XMLNS_ATTRIBUTE, ATOM_NAMESPACE) @@ -122,7 +119,7 @@ class MdmAtomModelRenderer extends AtomRenderer { https://validator.w3.org/feed/docs/atom.html#requiredEntryElements */ if (object.size() > 0) { - def mostRecentlyUpdated = object.max {it.lastUpdated} + def mostRecentlyUpdated = object.max { it.lastUpdated } writer.startNode(UPDATED_TAG) .characters(formatLastUpdated(mostRecentlyUpdated)) .end() @@ -320,7 +317,7 @@ class MdmAtomModelRenderer extends AtomRenderer { Optional fields */ writer.startNode(PUBLISHED_TAG) - .characters(formatDateCreated(object)) + .characters(formatAtomDate(object.dateFinalised)) .end() writer.startNode(CATEGORY_TAG) diff --git a/mdm-plugin-federation/grails-app/views/availableModel/_availableModel.gson b/mdm-plugin-federation/grails-app/views/availableModel/_availableModel.gson deleted file mode 100644 index 675564a18b..0000000000 --- a/mdm-plugin-federation/grails-app/views/availableModel/_availableModel.gson +++ /dev/null @@ -1,12 +0,0 @@ -import uk.ac.ox.softeng.maurodatamapper.federation.AvailableModel - -model { - AvailableModel availableModel -} - -json { - modelId availableModel.id - label availableModel.label - description availableModel.description - modelType availableModel.modelType -} diff --git a/mdm-plugin-federation/grails-app/views/publish/index.gson b/mdm-plugin-federation/grails-app/views/publish/index.gson new file mode 100644 index 0000000000..bd0e3bd3e0 --- /dev/null +++ b/mdm-plugin-federation/grails-app/views/publish/index.gson @@ -0,0 +1,18 @@ +import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority +import uk.ac.ox.softeng.maurodatamapper.federation.PublishedModel + +import java.time.OffsetDateTime + +model { + Authority authority + List publishedModels +} + +json { + authority { + label authority.label + url authority.url + } + lastUpdated publishedModels?.max { it.lastUpdated }?.lastUpdated ?: OffsetDateTime.now() + publishedModels tmpl.'/publishedModel/publishedModel'(publishedModels ?: []) +} diff --git a/mdm-plugin-federation/grails-app/views/publishedModel/_publishedModel.gson b/mdm-plugin-federation/grails-app/views/publishedModel/_publishedModel.gson new file mode 100644 index 0000000000..34518d332b --- /dev/null +++ b/mdm-plugin-federation/grails-app/views/publishedModel/_publishedModel.gson @@ -0,0 +1,16 @@ +import uk.ac.ox.softeng.maurodatamapper.federation.PublishedModel + +model { + PublishedModel publishedModel +} + +json { + modelId publishedModel.modelId + title publishedModel.title + modelType publishedModel.modelType + lastUpdated publishedModel.lastUpdated + dateCreated publishedModel.dateCreated + datePublished publishedModel.datePublished + if (publishedModel.author) author publishedModel.author + if (publishedModel.description) description publishedModel.description +} diff --git a/mdm-plugin-federation/grails-app/views/subscribedCatalogue/_fullSubscribedCatalogue.gson b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/_fullSubscribedCatalogue.gson new file mode 100644 index 0000000000..c44a24b610 --- /dev/null +++ b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/_fullSubscribedCatalogue.gson @@ -0,0 +1,23 @@ +import uk.ac.ox.softeng.maurodatamapper.federation.SubscribedCatalogue + +model { + SubscribedCatalogue subscribedCatalogue +} + +json { + id subscribedCatalogue.id + url subscribedCatalogue.url + label subscribedCatalogue.label + + if (subscribedCatalogue.description) { + description subscribedCatalogue.description + } + + if (subscribedCatalogue.refreshPeriod) { + refreshPeriod subscribedCatalogue.refreshPeriod + } + + if (subscribedCatalogue.apiKey) { + apiKey subscribedCatalogue.apiKey + } +} diff --git a/mdm-plugin-federation/grails-app/views/subscribedCatalogue/availableModels.gson b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/availableModels.gson deleted file mode 100644 index 3d0e125851..0000000000 --- a/mdm-plugin-federation/grails-app/views/subscribedCatalogue/availableModels.gson +++ /dev/null @@ -1,12 +0,0 @@ -import uk.ac.ox.softeng.maurodatamapper.federation.AvailableModel - -import grails.gorm.PagedResultList - -model { - Iterable availableModelList -} - -json { - count availableModelList instanceof PagedResultList ? ((PagedResultList) availableModelList).getTotalCount() : availableModelList?.size() ?: 0 - items g.render(availableModelList ?: []) -} \ No newline at end of file diff --git a/mdm-plugin-federation/grails-app/views/subscribedCatalogue/publishedModels.gson b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/publishedModels.gson new file mode 100644 index 0000000000..a468d6201b --- /dev/null +++ b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/publishedModels.gson @@ -0,0 +1,12 @@ +import uk.ac.ox.softeng.maurodatamapper.federation.PublishedModel + +import grails.gorm.PagedResultList + +model { + Iterable publishedModelList +} + +json { + count publishedModelList instanceof PagedResultList ? ((PagedResultList) publishedModelList).getTotalCount() : publishedModelList?.size() ?: 0 + items g.render(publishedModelList ?: []) +} \ No newline at end of file diff --git a/mdm-plugin-federation/grails-app/views/subscribedCatalogue/show.gson b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/show.gson index c44a24b610..903993c336 100644 --- a/mdm-plugin-federation/grails-app/views/subscribedCatalogue/show.gson +++ b/mdm-plugin-federation/grails-app/views/subscribedCatalogue/show.gson @@ -4,20 +4,4 @@ model { SubscribedCatalogue subscribedCatalogue } -json { - id subscribedCatalogue.id - url subscribedCatalogue.url - label subscribedCatalogue.label - - if (subscribedCatalogue.description) { - description subscribedCatalogue.description - } - - if (subscribedCatalogue.refreshPeriod) { - refreshPeriod subscribedCatalogue.refreshPeriod - } - - if (subscribedCatalogue.apiKey) { - apiKey subscribedCatalogue.apiKey - } -} +json tmpl.fullSubscribedCatalogue(subscribedCatalogue) diff --git a/mdm-plugin-federation/grails-app/views/subscribedModel/_fullSubscribedModel.gson b/mdm-plugin-federation/grails-app/views/subscribedModel/_fullSubscribedModel.gson new file mode 100644 index 0000000000..c04d9efcfc --- /dev/null +++ b/mdm-plugin-federation/grails-app/views/subscribedModel/_fullSubscribedModel.gson @@ -0,0 +1,18 @@ +import uk.ac.ox.softeng.maurodatamapper.federation.SubscribedModel + +model { + SubscribedModel subscribedModel +} + +json { + id subscribedModel.id + subscribedModelId subscribedModel.subscribedModelId + folderId subscribedModel.folderId + readableByEveryone subscribedModel.readableByEveryone + readableByAuthenticatedUsers subscribedModel.readableByAuthenticatedUsers + federated subscribedModel.localModelId ? true : false + if (subscribedModel.localModelId) { + localModelId subscribedModel.localModelId + lastRead subscribedModel.lastRead + } +} diff --git a/mdm-plugin-federation/grails-app/views/subscribedModel/_subscribedModel.gson b/mdm-plugin-federation/grails-app/views/subscribedModel/_subscribedModel.gson index 2410bc8f48..f312b4f925 100644 --- a/mdm-plugin-federation/grails-app/views/subscribedModel/_subscribedModel.gson +++ b/mdm-plugin-federation/grails-app/views/subscribedModel/_subscribedModel.gson @@ -8,4 +8,8 @@ json { id subscribedModel.id subscribedModelId subscribedModel.subscribedModelId folderId subscribedModel.folderId + federated subscribedModel.localModelId ? true : false + if (subscribedModel.localModelId) { + localModelId subscribedModel.localModelId + } } diff --git a/mdm-plugin-federation/grails-app/views/subscribedModel/show.gson b/mdm-plugin-federation/grails-app/views/subscribedModel/show.gson index 2410bc8f48..8ecedd9f67 100644 --- a/mdm-plugin-federation/grails-app/views/subscribedModel/show.gson +++ b/mdm-plugin-federation/grails-app/views/subscribedModel/show.gson @@ -4,8 +4,4 @@ model { SubscribedModel subscribedModel } -json { - id subscribedModel.id - subscribedModelId subscribedModel.subscribedModelId - folderId subscribedModel.folderId -} +json tmpl.fullSubscribedModel(subscribedModel) \ No newline at end of file diff --git a/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelFunctionalSpec.groovy b/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelFunctionalSpec.groovy index d9891e68f0..98591ac45d 100644 --- a/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelFunctionalSpec.groovy +++ b/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/SubscribedModelFunctionalSpec.groovy @@ -25,6 +25,7 @@ import grails.gorm.transactions.Transactional import grails.testing.mixin.integration.Integration import grails.testing.spock.OnceBefore import groovy.util.logging.Slf4j +import io.micronaut.http.HttpStatus import spock.lang.Shared /** @@ -54,31 +55,36 @@ class SubscribedModelFunctionalSpec extends ResourceFunctionalSpec localResponse = GET('all', STRING_ARG) + + then: + verifyResponse(HttpStatus.OK, localResponse) + log.warn(localResponse.body()) + } + + void 'test getting published models when model available'() { + given: + POST("folders/${folderId}/dataModels", [ + label : 'FunctionalTest DataModel', + readableByEveryone: true, + finalised : true, + dateFinalised : OffsetDateTimeConverter.toString(OffsetDateTime.now()), + modelVersion : '1.0.0' + ], MAP_ARG, true) + verifyResponse(HttpStatus.CREATED, response) + + when: + HttpResponse localResponse = GET('all', STRING_ARG) + + then: + verifyResponse(HttpStatus.OK, localResponse) + log.warn(localResponse.body()) + } +} diff --git a/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishFunctionalSpec.groovy b/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishFunctionalSpec.groovy new file mode 100644 index 0000000000..127cf5eefe --- /dev/null +++ b/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/publish/PublishFunctionalSpec.groovy @@ -0,0 +1,108 @@ +/* + * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package uk.ac.ox.softeng.maurodatamapper.federation.publish + +import uk.ac.ox.softeng.maurodatamapper.core.bootstrap.StandardEmailAddress +import uk.ac.ox.softeng.maurodatamapper.core.container.Folder +import uk.ac.ox.softeng.maurodatamapper.datamodel.DataModel +import uk.ac.ox.softeng.maurodatamapper.test.functional.BaseFunctionalSpec + +import grails.gorm.transactions.Transactional +import grails.testing.mixin.integration.Integration +import grails.testing.spock.OnceBefore +import groovy.util.logging.Slf4j +import io.micronaut.http.HttpStatus +import spock.lang.Shared + +@Slf4j +@Integration +class PublishFunctionalSpec extends BaseFunctionalSpec { + + @Shared + String folderId + + @Override + String getResourcePath() { + 'published' + } + + @OnceBefore + @Transactional + def checkAndSetupData() { + log.debug('Check and setup test data for FeedFunctionalSpec') + folderId = new Folder(label: 'Functional Test Folder', createdBy: StandardEmailAddress.FUNCTIONAL_TEST).save(flush: true).id.toString() + assert folderId + } + + @Transactional + def cleanupSpec() { + log.debug('CleanupSpec PublishFunctionalSpec') + cleanUpResources(DataModel, Folder) + } + + void 'test getting published models'() { + + when: + GET('models') + + then: + verifyResponse(HttpStatus.OK, response) + responseBody().authority.label == 'Mauro Data Mapper' + responseBody().authority.url == 'http://localhost' + responseBody().lastUpdated + responseBody().publishedModels.isEmpty() + + } + + void '2-test getting published models when model available'() { + given: + String publishedDateStr = '2021-06-28T12:36:37Z' + POST("folders/${folderId}/dataModels", [ + label : 'FunctionalTest DataModel', + readableByEveryone: true, + finalised : true, + dateFinalised : publishedDateStr, + description : 'Some random desc', + modelVersion : '1.0.0' + ], MAP_ARG, true) + verifyResponse(HttpStatus.CREATED, response) + String id = responseBody().id + + when: + GET('models') + + then: + verifyResponse(HttpStatus.OK, response) + responseBody().authority.label == 'Mauro Data Mapper' + responseBody().authority.url == 'http://localhost' + responseBody().lastUpdated + responseBody().publishedModels.size() == 1 + + when: + Map publishedModel = responseBody().publishedModels.first() + + then: + publishedModel.modelId == id + publishedModel.title == 'FunctionalTest DataModel 1.0.0' + publishedModel.description == 'Some random desc' + publishedModel.modelType == 'DataModel' + publishedModel.datePublished == publishedDateStr + publishedModel.lastUpdated + publishedModel.dateCreated + } +} diff --git a/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/test/BaseSubscribedModelServiceIntegrationSpec.groovy b/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/test/BaseSubscribedModelServiceIntegrationSpec.groovy index 8506f1ed83..3811a3302f 100644 --- a/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/test/BaseSubscribedModelServiceIntegrationSpec.groovy +++ b/mdm-plugin-federation/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/federation/test/BaseSubscribedModelServiceIntegrationSpec.groovy @@ -23,7 +23,7 @@ import uk.ac.ox.softeng.maurodatamapper.core.model.ModelService import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.ModelImporterProviderService import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.parameter.FileParameter import uk.ac.ox.softeng.maurodatamapper.core.provider.importer.parameter.ModelImporterProviderServiceParameters -import uk.ac.ox.softeng.maurodatamapper.federation.AvailableModel +import uk.ac.ox.softeng.maurodatamapper.federation.PublishedModel import uk.ac.ox.softeng.maurodatamapper.federation.SubscribedCatalogue import uk.ac.ox.softeng.maurodatamapper.federation.SubscribedModel import uk.ac.ox.softeng.maurodatamapper.federation.SubscribedModelService @@ -43,8 +43,8 @@ import java.time.OffsetDateTime @Rollback abstract class BaseSubscribedModelServiceIntegrationSpec extends BaseIntegrationSpec { - AvailableModel availableModelVersion1 - AvailableModel availableModelVersion2 + PublishedModel availableModelVersion1 + PublishedModel availableModelVersion2 SubscribedCatalogue subscribedCatalogue SubscribedModelService subscribedModelService @@ -79,14 +79,14 @@ abstract class BaseSubscribedModelServiceIntegrationSpec extend //Note: ID is hardcoded because we are mocking an external input rather than a domain created locally. //Don't need to save AvailableModel - availableModelVersion1 = new AvailableModel(id: Utils.toUuid("c8023de6-5329-4b8b-8a1b-27c2abeaffcd"), - label: 'Remote Model 1.0.0', + availableModelVersion1 = new PublishedModel(modelId: Utils.toUuid("c8023de6-5329-4b8b-8a1b-27c2abeaffcd"), + title: 'Remote Model 1.0.0', description: 'Remote Model Description', modelType: getModelType(), lastUpdated: OffsetDateTime.now()) - availableModelVersion2 = new AvailableModel(id: Utils.toUuid("d8023de6-5329-4b8b-8a1b-27c2abeaffcd"), - label: 'Remote Model 2.0.0', + availableModelVersion2 = new PublishedModel(modelId: Utils.toUuid("d8023de6-5329-4b8b-8a1b-27c2abeaffcd"), + title: 'Remote Model 2.0.0', description: 'Remote Model Description', modelType: getModelType(), lastUpdated: OffsetDateTime.now()) diff --git a/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/AvailableModel.groovy b/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/PublishedModel.groovy similarity index 62% rename from mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/AvailableModel.groovy rename to mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/PublishedModel.groovy index bcf7001f72..c96159f68d 100644 --- a/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/AvailableModel.groovy +++ b/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/PublishedModel.groovy @@ -17,21 +17,44 @@ */ package uk.ac.ox.softeng.maurodatamapper.federation +import uk.ac.ox.softeng.maurodatamapper.core.model.Model import uk.ac.ox.softeng.maurodatamapper.util.Version import java.time.OffsetDateTime -class AvailableModel { +class PublishedModel { - UUID id + UUID modelId String modelLabel Version modelVersion String description String modelType OffsetDateTime lastUpdated + OffsetDateTime dateCreated + OffsetDateTime datePublished + String author - void setLabel(String label) { + PublishedModel() { + } + + PublishedModel(Model model) { + modelId = model.id + modelLabel = model.label + modelVersion = model.modelVersion + modelType = model.domainType + lastUpdated = model.lastUpdated + dateCreated = model.dateCreated + datePublished = model.dateFinalised + author = model.author + description = model.description + } + + String getTitle() { + "${modelLabel} ${modelVersion}" + } + + void setTitle(String label) { String version = label.find(Version.VERSION_PATTERN) if (version) { modelVersion = Version.from(version) @@ -41,15 +64,11 @@ class AvailableModel { } } - String getLabel() { - "${modelLabel ?: ''} ${modelVersion ?: ''}".trim() - } - String getDescription() { - description == label ? null : description + description == title ? null : description } void setDescription(String description) { - if (description && description != modelLabel) this.description = description + if (description && description != title) this.description = description } } diff --git a/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/web/FederationClient.groovy b/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/web/FederationClient.groovy index 3adbc41507..7cfdd50c2d 100644 --- a/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/web/FederationClient.groovy +++ b/mdm-plugin-federation/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/federation/web/FederationClient.groovy @@ -42,6 +42,7 @@ import io.reactivex.Flowable import org.springframework.context.ApplicationContext import org.xml.sax.SAXException +import java.time.Duration import java.util.concurrent.ThreadFactory /** @@ -102,6 +103,7 @@ class FederationClient { MediaTypeCodecRegistry mediaTypeCodecRegistry) { this.hostUrl = hostUrl this.contextPath = contextPath + httpClientConfiguration.setReadTimeout(Duration.ofMinutes(5)) client = new DefaultHttpClient(LoadBalancer.fixed(hostUrl.toURL()), httpClientConfiguration, contextPath, @@ -112,15 +114,15 @@ class FederationClient { log.debug('Client created to connect to {}', hostUrl) } - boolean isConnectionPossible(UUID apiKey) { - getSubscribedCatalogueModels(apiKey) - } - - GPathResult getSubscribedCatalogueModels(UUID apiKey) { + GPathResult getSubscribedCatalogueModelsFromAtomFeed(UUID apiKey) { // Currently we use the ATOM feed which is XML and the micronaut client isnt designed to decode XML retrieveXmlDataFromClient(UriBuilder.of('feeds/all'), apiKey) } + Map getSubscribedCatalogueModels(UUID apiKey) { + retrieveMapFromClient(UriBuilder.of('published/models'), apiKey) + } + List> getAvailableExporters(UUID apiKey, String urlResourceType) { retrieveListFromClient(UriBuilder.of(urlResourceType).path('providers/exporters'), apiKey) } @@ -133,9 +135,9 @@ class FederationClient { retrieveStringFromClient(UriBuilder.of(urlResourceType) .path(resourceId.toString()) .path('export') - .path(exporterInfo.exporterNamespace) - .path(exporterInfo.exporterName) - .path(exporterInfo.exporterVersion), + .path(exporterInfo.namespace) + .path(exporterInfo.name) + .path(exporterInfo.version), apiKey ) } diff --git a/mdm-plugin-profile/build.gradle b/mdm-plugin-profile/build.gradle index 59f28bdc7d..14c84865f2 100644 --- a/mdm-plugin-profile/build.gradle +++ b/mdm-plugin-profile/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath "org.grails:grails-gradle-plugin:$grailsVersion" classpath "org.grails.plugins:hibernate5:$grailsHibernate5Version" classpath "org.grails.plugins:views-gradle:$grailsViewsVersion" + classpath "com.bertramlabs.plugins:asset-pipeline-gradle:$assetPipelineVersion" + } } @@ -24,6 +26,7 @@ apply plugin: "org.grails.grails-plugin" apply plugin: "org.grails.grails-plugin-publish" apply plugin: "org.grails.plugins.views-json" apply plugin: "org.grails.plugins.views-markup" +apply plugin: "com.bertramlabs.asset-pipeline" apply plugin: 'ox.softeng.grails' @@ -67,6 +70,7 @@ dependencies { compile "org.grails.plugins:views-json:$grailsViewsVersion" compile "org.grails.plugins:views-json-templates:$grailsViewsVersion" compile "org.grails.plugins:views-markup:$grailsViewsVersion" + compile "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" compileOnly "io.micronaut:micronaut-inject-groovy" console "org.grails:grails-console" // Until the fixes are published we need to use our versions of the profiles diff --git a/mdm-plugin-profile/grails-app/conf/application.yml b/mdm-plugin-profile/grails-app/conf/application.yml index 4863aa59e8..13a0ad8db0 100644 --- a/mdm-plugin-profile/grails-app/conf/application.yml +++ b/mdm-plugin-profile/grails-app/conf/application.yml @@ -113,7 +113,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: diff --git a/mdm-plugin-referencedata/build.gradle b/mdm-plugin-referencedata/build.gradle index 7105860b58..1925fd6168 100644 --- a/mdm-plugin-referencedata/build.gradle +++ b/mdm-plugin-referencedata/build.gradle @@ -11,6 +11,8 @@ buildscript { classpath "org.grails:grails-gradle-plugin:$grailsVersion" classpath "org.grails.plugins:hibernate5:$grailsHibernate5Version" classpath "org.grails.plugins:views-gradle:$grailsViewsVersion" + classpath "com.bertramlabs.plugins:asset-pipeline-gradle:$assetPipelineVersion" + } } @@ -24,6 +26,7 @@ apply plugin: "org.grails.grails-plugin" apply plugin: "org.grails.grails-plugin-publish" apply plugin: "org.grails.plugins.views-json" apply plugin: "org.grails.plugins.views-markup" +apply plugin: "com.bertramlabs.asset-pipeline" apply plugin: 'ox.softeng.grails' @@ -67,6 +70,7 @@ dependencies { compile "org.grails.plugins:views-json:$grailsViewsVersion" compile "org.grails.plugins:views-json-templates:$grailsViewsVersion" compile "org.grails.plugins:views-markup:$grailsViewsVersion" + compile "com.bertramlabs.plugins:asset-pipeline-grails:$assetPipelineVersion" compileOnly "io.micronaut:micronaut-inject-groovy" console "org.grails:grails-console" // Until the fixes are published we need to use our versions of the profiles diff --git a/mdm-plugin-referencedata/grails-app/conf/application.yml b/mdm-plugin-referencedata/grails-app/conf/application.yml index 70043b715e..36479db30f 100644 --- a/mdm-plugin-referencedata/grails-app/conf/application.yml +++ b/mdm-plugin-referencedata/grails-app/conf/application.yml @@ -113,7 +113,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: cache: @@ -150,6 +160,10 @@ dataSource: --- environments: test: + maurodatamapper: + authority: + name: 'Test Authority' + url: 'http://localhost' spring.flyway.enabled: false database: name: 'mpdTest' diff --git a/mdm-plugin-referencedata/grails-app/services/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelService.groovy b/mdm-plugin-referencedata/grails-app/services/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelService.groovy index f2cf9fed34..aef89d4baa 100644 --- a/mdm-plugin-referencedata/grails-app/services/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelService.groovy +++ b/mdm-plugin-referencedata/grails-app/services/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelService.groovy @@ -75,20 +75,25 @@ class ReferenceDataModelService extends ModelService impleme @Override List getAll(Collection ids) { - ReferenceDataModel.getAll(ids).findAll() + ReferenceDataModel.getAll(ids).findAll().collect {unwrapIfProxy(it)} } @Override - List list(Map pagination = [:]) { + List list(Map pagination) { ReferenceDataModel.list(pagination) } + @Override + List list() { + ReferenceDataModel.list().collect {unwrapIfProxy(it)} + } + Long count() { ReferenceDataModel.count() } - int countByLabel(String label) { - ReferenceDataModel.countByLabel(label) + int countByAuthorityAndLabel(Authority authority, String label) { + ReferenceDataModel.countByAuthorityAndLabel(authority, label) } ReferenceDataModel validate(ReferenceDataModel referenceDataModel) { @@ -274,8 +279,8 @@ class ReferenceDataModelService extends ModelService impleme ReferenceDataModel.findAllByReadableByAuthenticatedUsers(true) } - List findAllByLabel(String label) { - ReferenceDataModel.findAllByLabel(label) + List findAllByAuthorityAndLabel(Authority authority, String label) { + ReferenceDataModel.findAllByAuthorityAndLabel(authority, label) } @Override @@ -295,8 +300,14 @@ class ReferenceDataModelService extends ModelService impleme ReferenceDataModel.byDeleted().list(pagination) } - int countAllByLabelAndBranchNameAndNotFinalised(String label, String branchName) { - ReferenceDataModel.countByLabelAndBranchNameAndFinalised(label, branchName, false) + @Override + int countByAuthorityAndLabelAndBranchNameAndNotFinalised(Authority authority, String label, String branchName) { + ReferenceDataModel.countByAuthorityAndLabelAndBranchNameAndFinalised(authority, label, branchName, false) + } + + @Override + int countByAuthorityAndLabelAndVersion(Authority authority, String label, Version modelVersion) { + ReferenceDataModel.countByAuthorityAndLabelAndModelVersion(authority, label, modelVersion) } ReferenceDataModel findLatestByDataLoaderPlugin(DataLoaderProviderService dataLoaderProviderService) { diff --git a/mdm-plugin-referencedata/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelFunctionalSpec.groovy b/mdm-plugin-referencedata/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelFunctionalSpec.groovy index c33213c2f4..de84c7dfd9 100644 --- a/mdm-plugin-referencedata/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelFunctionalSpec.groovy +++ b/mdm-plugin-referencedata/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/referencedata/ReferenceDataModelFunctionalSpec.groovy @@ -89,7 +89,7 @@ class ReferenceDataModelFunctionalSpec extends ResourceFunctionalSpec target.label ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_FORK_OF' FROM data WHERE vl.id = data.vl_id; @@ -21,12 +21,12 @@ WITH data AS ( SELECT vl.id AS vl_id, source.id AS new_target_id, target.id AS new_source_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.terminology.terminology source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.terminology.terminology target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN terminology.terminology source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN terminology.terminology target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'SUPERSEDED_BY_MODEL' ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_FORK_OF', target_model_id = new_target_id, multi_facet_aware_item_id = new_source_id @@ -38,12 +38,12 @@ WHERE data.vl_id = vl.id WITH data AS ( SELECT vl.id AS vl_id, vl.multi_facet_aware_item_id AS new_source_id - FROM maurodatamapper.terminology.join_terminology_to_facet jt - INNER JOIN maurodatamapper.core.version_link vl ON jt.version_link_id = vl.id + FROM terminology.join_terminology_to_facet jt + INNER JOIN core.version_link vl ON jt.version_link_id = vl.id WHERE vl.link_type = 'NEW_FORK_OF' AND jt.terminology_id <> vl.multi_facet_aware_item_id ) -UPDATE maurodatamapper.terminology.join_terminology_to_facet jt +UPDATE terminology.join_terminology_to_facet jt SET terminology_id = data.new_source_id FROM data WHERE jt.terminology_id <> new_source_id AND @@ -56,12 +56,12 @@ WITH data AS ( SELECT vl.id AS vl_id, source.id AS new_target_id, target.id AS new_source_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.terminology.terminology source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.terminology.terminology target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN terminology.terminology source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN terminology.terminology target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'SUPERSEDED_BY_DOCUMENTATION' ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_DOCUMENTATION_VERSION_OF', target_model_id = new_target_id, multi_facet_aware_item_id = new_source_id @@ -73,12 +73,12 @@ WHERE data.vl_id = vl.id WITH data AS ( SELECT vl.id AS vl_id, vl.multi_facet_aware_item_id AS new_source_id - FROM maurodatamapper.terminology.join_terminology_to_facet jt - INNER JOIN maurodatamapper.core.version_link vl ON jt.version_link_id = vl.id + FROM terminology.join_terminology_to_facet jt + INNER JOIN core.version_link vl ON jt.version_link_id = vl.id WHERE vl.link_type = 'NEW_DOCUMENTATION_VERSION_OF' AND jt.terminology_id <> vl.multi_facet_aware_item_id ) -UPDATE maurodatamapper.terminology.join_terminology_to_facet jt +UPDATE terminology.join_terminology_to_facet jt SET terminology_id = data.new_source_id FROM data WHERE jt.terminology_id <> new_source_id AND @@ -89,13 +89,13 @@ WHERE jt.terminology_id <> new_source_id AND -- Simple one, just update the link type to the correct fork type where the labels dont match WITH data AS ( SELECT vl.id AS vl_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.terminology.code_set source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.terminology.code_set target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN terminology.code_set source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN terminology.code_set target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'NEW_MODEL_VERSION_OF' AND source.label <> target.label ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_FORK_OF' FROM data WHERE vl.id = data.vl_id; @@ -106,12 +106,12 @@ WITH data AS ( SELECT vl.id AS vl_id, source.id AS new_target_id, target.id AS new_source_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.terminology.code_set source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.terminology.code_set target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN terminology.code_set source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN terminology.code_set target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'SUPERSEDED_BY_MODEL' ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_FORK_OF', target_model_id = new_target_id, multi_facet_aware_item_id = new_source_id @@ -123,12 +123,12 @@ WHERE data.vl_id = vl.id WITH data AS ( SELECT vl.id AS vl_id, vl.multi_facet_aware_item_id AS new_source_id - FROM maurodatamapper.terminology.join_codeset_to_facet jt - INNER JOIN maurodatamapper.core.version_link vl ON jt.version_link_id = vl.id + FROM terminology.join_codeset_to_facet jt + INNER JOIN core.version_link vl ON jt.version_link_id = vl.id WHERE vl.link_type = 'NEW_FORK_OF' AND jt.codeset_id <> vl.multi_facet_aware_item_id ) -UPDATE maurodatamapper.terminology.join_codeset_to_facet jt +UPDATE terminology.join_codeset_to_facet jt SET codeset_id = data.new_source_id FROM data WHERE jt.codeset_id <> new_source_id AND @@ -141,12 +141,12 @@ WITH data AS ( SELECT vl.id AS vl_id, source.id AS new_target_id, target.id AS new_source_id - FROM maurodatamapper.core.version_link vl - LEFT JOIN maurodatamapper.terminology.code_set source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' - LEFT JOIN maurodatamapper.terminology.code_set target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' + FROM core.version_link vl + LEFT JOIN terminology.code_set source ON source.id = vl.multi_facet_aware_item_id AND vl.multi_facet_aware_item_domain_type = 'DataModel' + LEFT JOIN terminology.code_set target ON target.id = vl.target_model_id AND vl.target_model_domain_type = 'DataModel' WHERE link_type = 'SUPERSEDED_BY_DOCUMENTATION' ) -UPDATE maurodatamapper.core.version_link vl +UPDATE core.version_link vl SET link_type = 'NEW_DOCUMENTATION_VERSION_OF', target_model_id = new_target_id, multi_facet_aware_item_id = new_source_id @@ -158,12 +158,12 @@ WHERE data.vl_id = vl.id WITH data AS ( SELECT vl.id AS vl_id, vl.multi_facet_aware_item_id AS new_source_id - FROM maurodatamapper.terminology.join_codeset_to_facet jt - INNER JOIN maurodatamapper.core.version_link vl ON jt.version_link_id = vl.id + FROM terminology.join_codeset_to_facet jt + INNER JOIN core.version_link vl ON jt.version_link_id = vl.id WHERE vl.link_type = 'NEW_DOCUMENTATION_VERSION_OF' AND jt.codeset_id <> vl.multi_facet_aware_item_id ) -UPDATE maurodatamapper.terminology.join_codeset_to_facet jt +UPDATE terminology.join_codeset_to_facet jt SET codeset_id = data.new_source_id FROM data WHERE jt.codeset_id <> new_source_id AND diff --git a/mdm-plugin-terminology/grails-app/conf/db/migration/terminology/beforeValidate.sql b/mdm-plugin-terminology/grails-app/conf/db/migration/terminology/beforeValidate.sql new file mode 100644 index 0000000000..41bd15f15f --- /dev/null +++ b/mdm-plugin-terminology/grails-app/conf/db/migration/terminology/beforeValidate.sql @@ -0,0 +1,4 @@ +UPDATE terminology.flyway_schema_history +SET checksum = 372732891 +WHERE version = '2.1.0' AND + checksum = 1698670300; diff --git a/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetService.groovy b/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetService.groovy index e2c3114323..bfeee0e9d3 100644 --- a/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetService.groovy +++ b/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetService.groovy @@ -20,6 +20,7 @@ package uk.ac.ox.softeng.maurodatamapper.terminology import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiBadRequestException import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiInvalidModelException import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiNotYetImplementedException +import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority import uk.ac.ox.softeng.maurodatamapper.core.container.Classifier import uk.ac.ox.softeng.maurodatamapper.core.container.Folder import uk.ac.ox.softeng.maurodatamapper.core.facet.EditTitle @@ -63,14 +64,19 @@ class CodeSetService extends ModelService { @Override List getAll(Collection ids) { - CodeSet.getAll(ids).findAll() + CodeSet.getAll(ids).findAll().collect {unwrapIfProxy(it)} } @Override - List list(Map pagination = [:]) { + List list(Map pagination) { CodeSet.list(pagination) } + @Override + List list() { + CodeSet.list().collect {unwrapIfProxy(it)} + } + @Override boolean handlesPathPrefix(String pathPrefix) { pathPrefix == "cs" @@ -85,8 +91,8 @@ class CodeSetService extends ModelService { CodeSet.count() } - int countByLabel(String label) { - CodeSet.countByLabel(label) + int countByAuthorityAndLabel(Authority authority, String label) { + CodeSet.countByAuthorityAndLabel(authority, label) } CodeSet validate(CodeSet codeSet) { @@ -158,8 +164,8 @@ class CodeSetService extends ModelService { CodeSet.findAllByReadableByAuthenticatedUsers(true) } - List findAllByLabel(String label) { - CodeSet.findAllByLabel(label) + List findAllByAuthorityAndLabel(Authority authority, String label) { + CodeSet.findAllByAuthorityAndLabel(authority, label) } @Override @@ -179,8 +185,9 @@ class CodeSetService extends ModelService { CodeSet.byDeleted().list(pagination) } - int countAllByLabelAndBranchNameAndNotFinalised(String label, String branchName) { - CodeSet.countByLabelAndBranchNameAndFinalised(label, branchName, false) + @Override + int countByAuthorityAndLabelAndBranchNameAndNotFinalised(Authority authority, String label, String branchName) { + CodeSet.countByAuthorityAndLabelAndBranchNameAndFinalised(authority, label, branchName, false) } CodeSet findLatestByDataLoaderPlugin(DataLoaderProviderService dataLoaderProviderService) { @@ -394,6 +401,11 @@ class CodeSetService extends ModelService { CodeSet.byIdInList(findAllModelSupersededIds(userSecurityPolicyManager.listReadableSecuredResourceIds(CodeSet))).list(pagination) } + @Override + int countByAuthorityAndLabelAndVersion(Authority authority, String label, Version modelVersion) { + CodeSet.countByAuthorityAndLabelAndModelVersion(authority, label, modelVersion) + } + /** * Find all resources by the defined user security policy manager. If none provided then assume no security policy in place in which case * everything is public. diff --git a/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyService.groovy b/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyService.groovy index 80885946d5..18ee9180dd 100644 --- a/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyService.groovy +++ b/mdm-plugin-terminology/grails-app/services/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyService.groovy @@ -20,6 +20,7 @@ package uk.ac.ox.softeng.maurodatamapper.terminology import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiInternalException import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiInvalidModelException import uk.ac.ox.softeng.maurodatamapper.api.exception.ApiNotYetImplementedException +import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority import uk.ac.ox.softeng.maurodatamapper.core.container.Classifier import uk.ac.ox.softeng.maurodatamapper.core.container.Folder import uk.ac.ox.softeng.maurodatamapper.core.facet.EditTitle @@ -63,14 +64,19 @@ class TerminologyService extends ModelService { @Override List getAll(Collection ids) { - Terminology.getAll(ids).findAll() + Terminology.getAll(ids).findAll().collect {unwrapIfProxy(it)} } @Override - List list(Map pagination = [:]) { + List list(Map pagination) { Terminology.list(pagination) } + @Override + List list() { + Terminology.list().collect {unwrapIfProxy(it)} + } + @Override boolean handlesPathPrefix(String pathPrefix) { pathPrefix == "te" @@ -85,8 +91,8 @@ class TerminologyService extends ModelService { Terminology.count() } - int countByLabel(String label) { - Terminology.countByLabel(label) + int countByAuthorityAndLabel(Authority authority, String label) { + Terminology.countByAuthorityAndLabel(authority, label) } Terminology validate(Terminology terminology) { @@ -273,8 +279,8 @@ class TerminologyService extends ModelService { Terminology.findAllByReadableByAuthenticatedUsers(true) } - List findAllByLabel(String label) { - Terminology.findAllByLabel(label) + List findAllByAuthorityAndLabel(Authority authority, String label) { + Terminology.findAllByAuthorityAndLabel(authority, label) } @Override @@ -304,8 +310,14 @@ class TerminologyService extends ModelService { Terminology.byDeleted().list(pagination) } - int countAllByLabelAndBranchNameAndNotFinalised(String label, String branchName) { - Terminology.countByLabelAndBranchNameAndFinalised(label, branchName, false) + @Override + int countByAuthorityAndLabelAndBranchNameAndNotFinalised(Authority authority, String label, String branchName) { + Terminology.countByAuthorityAndLabelAndBranchNameAndFinalised(authority, label, branchName, false) + } + + @Override + int countByAuthorityAndLabelAndVersion(Authority authority, String label, Version modelVersion) { + Terminology.countByAuthorityAndLabelAndModelVersion(authority, label, modelVersion) } Terminology findLatestByDataLoaderPlugin(DataLoaderProviderService dataLoaderProviderService) { diff --git a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetFunctionalSpec.groovy b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetFunctionalSpec.groovy index cf9191d6b5..74208c0a12 100644 --- a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetFunctionalSpec.groovy +++ b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/CodeSetFunctionalSpec.groovy @@ -160,7 +160,7 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }''' } @@ -1892,7 +1892,7 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }, "exportMetadata": { @@ -1947,7 +1947,7 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } } ] @@ -1978,7 +1978,7 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }, "exportMetadata": { @@ -2039,7 +2039,7 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } } ] @@ -2066,7 +2066,6 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { def id = response.body().items[0].id String expected = new String(loadTestFile('simpleCodeSet')).replaceFirst('"exportedBy": "Admin User",', '"exportedBy": "Unlogged User",') - .replace(/Test Authority/, 'Mauro Data Mapper') expect: @@ -2133,7 +2132,6 @@ class CodeSetFunctionalSpec extends ResourceFunctionalSpec { String expected = new String(loadTestFile('codeSetFunctionalTest')).replaceFirst('"exportedBy": "Admin User",', '"exportedBy": "Unlogged User",') - .replace(/Test Authority/, 'Mauro Data Mapper') expect: diff --git a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyFunctionalSpec.groovy b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyFunctionalSpec.groovy index b7efa30542..c9ae3b944d 100644 --- a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyFunctionalSpec.groovy +++ b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/TerminologyFunctionalSpec.groovy @@ -155,7 +155,7 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }''' } @@ -2128,7 +2128,7 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }, "exportMetadata": { @@ -2183,7 +2183,7 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } } ] @@ -2214,7 +2214,7 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } }, "exportMetadata": { @@ -2275,7 +2275,7 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { "authority": { "id": "${json-unit.matches:id}", "url": "http://localhost", - "label": "Mauro Data Mapper" + "label": "Test Authority" } } ] @@ -2309,7 +2309,6 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { def id = response.body().items[0].id String expected = new String(loadTestFile('simpleTerminology')).replaceFirst('"exportedBy": "Admin User",', '"exportedBy": "Unlogged User",') - .replace(/Test Authority/, 'Mauro Data Mapper') expect: @@ -2365,7 +2364,6 @@ class TerminologyFunctionalSpec extends ResourceFunctionalSpec { def id = response.body().items[0].id String expected = new String(loadTestFile('complexTerminology')).replaceFirst('"exportedBy": "Admin User",', '"exportedBy": "Unlogged User",') - .replace(/Test Authority/, 'Mauro Data Mapper') expect: id diff --git a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/JsonCodeSetImporterExporterServiceSpec.groovy b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/JsonCodeSetImporterExporterServiceSpec.groovy index 9548eaac4c..06b8f7c196 100644 --- a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/JsonCodeSetImporterExporterServiceSpec.groovy +++ b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/JsonCodeSetImporterExporterServiceSpec.groovy @@ -96,7 +96,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec Assert.fail("Expected export file ${expectedPath} does not exist") } - String expectedJson = replaceContentWithMatchers(Files.readString(expectedPath)).replace(/Test Authority/, 'Mauro Data Mapper') + String expectedJson = replaceContentWithMatchers(Files.readString(expectedPath)) verifyJson(expectedJson, exportedModel) } @@ -190,7 +190,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec String exported = exportModel(simpleCodeSetId) then: - validateExportedModel('bootstrappedSimpleCodeSet', exported.replace(/Test Authority/, 'Mauro Data Mapper')) + validateExportedModel('bootstrappedSimpleCodeSet', exported) //note: importing does not actually save when: @@ -244,7 +244,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -279,7 +279,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.documentationVersion.toString() == '1.0.0' cs.modelVersion.toString() == '6.3.1' cs.finalised == true - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -313,7 +313,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' cs.aliases.size() == 2 'Alias 1' in cs.aliases @@ -328,7 +328,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec then: validateExportedModel('codeSetSimpleWithAliases', exported) - } + } void 'test simple data import with annotations'() { given: @@ -349,7 +349,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases cs.annotations.size() == 1 @@ -362,7 +362,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec then: ann.description == 'test annotation 1 description' - ann.label == 'test annotation 1 label' + ann.label == 'test annotation 1 label' when: String exported = exportModel(cs.id) @@ -390,7 +390,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -424,7 +424,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -458,7 +458,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -470,7 +470,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec Term term0 = cs.terms[0] then: - term0.label == 'STT01: Simple Test Term 01' + term0.label == 'STT01: Simple Test Term 01' when: String exported = exportModel(cs.id) @@ -498,7 +498,7 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -556,5 +556,5 @@ class JsonCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec then: ApiBadRequestException exception = thrown(ApiBadRequestException) exception.errorCode == 'CSS01' - } + } } diff --git a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/XmlCodeSetImporterExporterServiceSpec.groovy b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/XmlCodeSetImporterExporterServiceSpec.groovy index 1009966a2e..5405f3d447 100644 --- a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/XmlCodeSetImporterExporterServiceSpec.groovy +++ b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/provider/XmlCodeSetImporterExporterServiceSpec.groovy @@ -189,7 +189,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i String exported = exportModel(simpleCodeSetId) then: - validateExportedModel('bootstrappedSimpleCodeSet', exported.replace(/Test Authority/, 'Mauro Data Mapper')) + validateExportedModel('bootstrappedSimpleCodeSet', exported) //note: importing does not actually save when: @@ -243,7 +243,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -278,7 +278,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.documentationVersion.toString() == '1.0.0' cs.modelVersion.toString() == '6.3.1' cs.finalised == true - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -312,7 +312,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' cs.aliases.size() == 2 'Alias 1' in cs.aliases @@ -327,7 +327,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i then: validateExportedModel('codeSetSimpleWithAliases', exported) - } + } void 'test simple data import with annotations'() { given: @@ -348,7 +348,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases cs.annotations.size() == 1 @@ -361,7 +361,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i then: ann.description == 'test annotation 1 description' - ann.label == 'test annotation 1 label' + ann.label == 'test annotation 1 label' when: String exported = exportModel(cs.id) @@ -389,7 +389,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -423,7 +423,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -457,7 +457,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -469,7 +469,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i Term term0 = cs.terms[0] then: - term0.label == 'STT01: Simple Test Term 01' + term0.label == 'STT01: Simple Test Term 01' when: String exported = exportModel(cs.id) @@ -497,7 +497,7 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i cs.organisation == 'Test Organisation' cs.documentationVersion.toString() == '1.0.0' cs.finalised == false - cs.authority.label == 'Mauro Data Mapper' + cs.authority.label == 'Test Authority' cs.authority.url == 'http://localhost' !cs.aliases !cs.annotations @@ -555,5 +555,5 @@ class XmlCodeSetImporterExporterServiceSpec extends BaseCodeSetIntegrationSpec i then: ApiBadRequestException exception = thrown(ApiBadRequestException) exception.errorCode == 'CSS01' - } + } } diff --git a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermFunctionalSpec.groovy b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermFunctionalSpec.groovy index 24ed3189c2..bb6133990c 100644 --- a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermFunctionalSpec.groovy +++ b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermFunctionalSpec.groovy @@ -80,7 +80,7 @@ class TermFunctionalSpec extends ResourceFunctionalSpec { @Transactional def checkAndSetupData() { log.debug('Check and setup test data') - Authority testAuthority = new Authority(label: 'Test Authority', url: "https://localhost", createdBy: FUNCTIONAL_TEST) + Authority testAuthority = Authority.findByLabel('Test Authority') checkAndSave(testAuthority) folder = new Folder(label: 'Functional Test Folder', createdBy: FUNCTIONAL_TEST) checkAndSave(folder) @@ -98,7 +98,6 @@ class TermFunctionalSpec extends ResourceFunctionalSpec { def cleanupSpec() { log.debug('CleanupSpec TermFunctionalSpec') cleanUpResources(TermRelationship, Term, TermRelationshipType, Terminology, Folder) - Authority.findByLabel('Test Authority').delete(flush: true) } @Override diff --git a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermRelationshipTypeFunctionalSpec.groovy b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermRelationshipTypeFunctionalSpec.groovy index 4b5db48827..f8596d907c 100644 --- a/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermRelationshipTypeFunctionalSpec.groovy +++ b/mdm-plugin-terminology/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/terminology/term/TermRelationshipTypeFunctionalSpec.groovy @@ -59,7 +59,7 @@ class TermRelationshipTypeFunctionalSpec extends ResourceFunctionalSpec 1), @@ -15,10 +15,10 @@ WITH dups AS (SELECT securable_resource_id, srgr.group_role_id, gr.display_name, ROW_NUMBER() OVER (PARTITION BY dups.securable_resource_id, dups.user_group_id ORDER BY display_name, gr.date_created) AS rank - FROM maurodatamapper.security.securable_resource_group_role srgr + FROM security.securable_resource_group_role srgr INNER JOIN dups ON dups.securable_resource_id = srgr.securable_resource_id AND dups.user_group_id = srgr.user_group_id - INNER JOIN maurodatamapper.security.group_role gr ON gr.id = srgr.group_role_id) + INNER JOIN security.group_role gr ON gr.id = srgr.group_role_id) DELETE -FROM maurodatamapper.security.securable_resource_group_role +FROM security.securable_resource_group_role WHERE id IN (SELECT id FROM ranked); \ No newline at end of file diff --git a/mdm-security/grails-app/conf/db/migration/security/V2_2_0__add_creation_method_to_users.sql b/mdm-security/grails-app/conf/db/migration/security/V2_2_0__add_creation_method_to_users.sql new file mode 100644 index 0000000000..549ee2ab46 --- /dev/null +++ b/mdm-security/grails-app/conf/db/migration/security/V2_2_0__add_creation_method_to_users.sql @@ -0,0 +1,8 @@ +ALTER TABLE security.catalogue_user + ADD creation_method VARCHAR(255); + +UPDATE security.catalogue_user +SET creation_method = 'Standard'; + +ALTER TABLE security.catalogue_user + ALTER COLUMN creation_method SET NOT NULL; \ No newline at end of file diff --git a/mdm-security/grails-app/conf/db/migration/security/beforeValidate.sql b/mdm-security/grails-app/conf/db/migration/security/beforeValidate.sql new file mode 100644 index 0000000000..0018e84089 --- /dev/null +++ b/mdm-security/grails-app/conf/db/migration/security/beforeValidate.sql @@ -0,0 +1,4 @@ +UPDATE security.flyway_schema_history +SET checksum = -1673556944 +WHERE version = '2.1.0' AND + checksum = 1304140234; diff --git a/mdm-security/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserController.groovy b/mdm-security/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserController.groovy index cbd0327231..6ec22904e0 100644 --- a/mdm-security/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserController.groovy +++ b/mdm-security/grails-app/controllers/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserController.groovy @@ -31,9 +31,6 @@ import uk.ac.ox.softeng.maurodatamapper.util.Utils import grails.gorm.transactions.Transactional import org.springframework.http.HttpStatus -import java.nio.file.Path -import java.nio.file.Paths - import static uk.ac.ox.softeng.maurodatamapper.core.admin.ApiPropertyEnum.EMAIL_ADMIN_CONFIRM_REGISTRATION_BODY import static uk.ac.ox.softeng.maurodatamapper.core.admin.ApiPropertyEnum.EMAIL_ADMIN_CONFIRM_REGISTRATION_SUBJECT import static uk.ac.ox.softeng.maurodatamapper.core.admin.ApiPropertyEnum.EMAIL_ADMIN_REGISTER_BODY @@ -162,7 +159,9 @@ class CatalogueUserController extends EditLoggingController /* im CatalogueUser user = catalogueUserService.findByEmailAddress(params.emailAddress) // if no user, don't respond with notfound as this can be used to mine the DB - if (!user) return done() + // if no pwd set then also return as this means the user has not completed registration or has logged in using an alternative method + // Only an admin user can reset a users pwd in this situation + if (!user || !user.password) return done() // As this requires calls to database we only want to call it once in the method String siteUrlString = siteUrl diff --git a/mdm-security/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUser.groovy b/mdm-security/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUser.groovy index f8e9c8d594..1954910fbc 100644 --- a/mdm-security/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUser.groovy +++ b/mdm-security/grails-app/domain/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUser.groovy @@ -55,6 +55,7 @@ class CatalogueUser implements Principal, EditHistoryAware, CreatorAware, User { String userPreferences Boolean disabled UUID resetToken + String creationMethod static belongsTo = UserGroup @@ -105,6 +106,7 @@ class CatalogueUser implements Principal, EditHistoryAware, CreatorAware, User { } def beforeValidate() { + if (!creationMethod) creationMethod = 'Standard' if (pending == null) pending = false if (disabled == null) disabled = false } diff --git a/mdm-security/grails-app/services/uk/ac/ox/softeng/maurodatamapper/security/role/VirtualSecurableResourceGroupRoleService.groovy b/mdm-security/grails-app/services/uk/ac/ox/softeng/maurodatamapper/security/role/VirtualSecurableResourceGroupRoleService.groovy index cc85e50d3e..3c4a241b8c 100644 --- a/mdm-security/grails-app/services/uk/ac/ox/softeng/maurodatamapper/security/role/VirtualSecurableResourceGroupRoleService.groovy +++ b/mdm-security/grails-app/services/uk/ac/ox/softeng/maurodatamapper/security/role/VirtualSecurableResourceGroupRoleService.groovy @@ -26,6 +26,9 @@ import uk.ac.ox.softeng.maurodatamapper.core.model.Model import uk.ac.ox.softeng.maurodatamapper.security.SecurableResource import uk.ac.ox.softeng.maurodatamapper.util.Utils +import grails.gorm.transactions.Transactional + +@Transactional class VirtualSecurableResourceGroupRoleService { VersionedFolderService versionedFolderService diff --git a/mdm-security/grails-app/views/catalogueUser/_fullCatalogueUser.gson b/mdm-security/grails-app/views/catalogueUser/_fullCatalogueUser.gson index 3d21d5d180..0a5c9ca601 100644 --- a/mdm-security/grails-app/views/catalogueUser/_fullCatalogueUser.gson +++ b/mdm-security/grails-app/views/catalogueUser/_fullCatalogueUser.gson @@ -9,6 +9,7 @@ model { json { + creationMethod catalogueUser.creationMethod availableActions userSecurityPolicyManager.userAvailableActions(CatalogueUser, catalogueUser.id) if (catalogueUser.lastLogin) lastLogin catalogueUser.lastLogin diff --git a/mdm-security/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserFunctionalSpec.groovy b/mdm-security/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserFunctionalSpec.groovy index e3ac6fb950..139a977ec3 100644 --- a/mdm-security/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserFunctionalSpec.groovy +++ b/mdm-security/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserFunctionalSpec.groovy @@ -184,6 +184,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "availableActions": ["show"], "disabled": false, "id": "${json-unit.matches:id}", + "creationMethod": "Standard", "pending": true }''' } @@ -205,6 +206,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "pending": false, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] }, { @@ -216,6 +218,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] }, { @@ -227,6 +230,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] }, { @@ -237,6 +241,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "pending": false, "disabled": false, "createdBy": "admin@maurodatamapper.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"], "organisation": "Oxford BRC Informatics", "jobTitle": "God", @@ -255,6 +260,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "pending": false, "disabled": false, "createdBy": "unlogged_user@mdm-core.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] }, { @@ -265,6 +271,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "pending": true, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"], "organisation": "Oxford", "jobTitle": "tester" @@ -278,6 +285,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] }, { @@ -288,6 +296,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "pending": false, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] }, { @@ -299,6 +308,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["update","disable","show"] } ] @@ -359,6 +369,7 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "disabled": false, "id": "${json-unit.matches:id}", "createdBy": "admin@maurodatamapper.com", + "creationMethod": "Standard", "pending": false }''' } @@ -394,7 +405,8 @@ class CatalogueUserFunctionalSpec extends BaseFunctionalSpec implements Security "pending": false, "createdBy": "functional-test@test.com", "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }''' } diff --git a/mdm-security/src/test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserControllerSpec.groovy b/mdm-security/src/test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserControllerSpec.groovy index 3bcf3d85dc..585314feb7 100644 --- a/mdm-security/src/test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserControllerSpec.groovy +++ b/mdm-security/src/test/groovy/uk/ac/ox/softeng/maurodatamapper/security/CatalogueUserControllerSpec.groovy @@ -135,7 +135,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec } ], "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -145,7 +146,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "createdBy": "unit-test@test.com", "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -157,7 +159,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "jobTitle": "tester", "organisation": "Oxford", "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -168,7 +171,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "createdBy": "unit-test@test.com", "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -179,7 +183,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "createdBy": "unit-test@test.com", "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -190,7 +195,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "createdBy": "unit-test@test.com", "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -201,7 +207,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "createdBy": "unit-test@test.com", "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }, { "availableActions": [ "delete", "show", "update"], @@ -211,7 +218,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "createdBy": "unit-test@test.com", "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" } ] }''' @@ -246,7 +254,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "disabled": false, "id": "${json-unit.matches:id}", "pending": true, - "createdBy":"unit-test@test.com" + "createdBy":"unit-test@test.com", + "creationMethod": "Standard" }''' } @@ -261,7 +270,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "id": "${json-unit.matches:id}", "pending": false, "needsToResetPassword": true, - "createdBy": "unit-test@test.com" + "createdBy": "unit-test@test.com", + "creationMethod": "Standard" }''' } @@ -284,7 +294,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "organisation": "unit test", "jobTitle": "tester", "needsToResetPassword": true, - "createdBy": "unit-test@test.com" + "createdBy": "unit-test@test.com", + "creationMethod": "Standard" }''' } @@ -328,7 +339,8 @@ class CatalogueUserControllerSpec extends ResourceControllerSpec "id": "${json-unit.matches:id}", "pending": false, "needsToResetPassword": true, - "createdBy": "unit-test@test.com" + "createdBy": "unit-test@test.com", + "creationMethod": "Standard" }''' } @@ -595,7 +607,8 @@ json { "jobTitle": "tester", "organisation": "Oxford", "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" } ] }''' @@ -690,7 +703,8 @@ json { "availableActions": ["delete","show","update"], "pending": true, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }''' // Invalid invalidUnsavedInstance || UNPROCESSABLE_ENTITY | '{"total":1, "errors": [' + @@ -748,7 +762,8 @@ json { "availableActions": ["delete","show","update"], "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }''' when: @@ -811,7 +826,8 @@ json { "disabled": false, "id": "${json-unit.matches:id}", "pending": false, - "createdBy": "unit-test@test.com" + "createdBy": "unit-test@test.com", + "creationMethod": "Standard" }''' userName = userEmail ?: 'No user' @@ -871,7 +887,8 @@ json { "jobTitle": "tester", "organisation": "Oxford", "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }''' // Invalid userEmailAddresses.reader || UNPROCESSABLE_ENTITY | '{"total":1, "errors": [{"message"' + @@ -931,7 +948,8 @@ json { "jobTitle": "tester", "organisation": "Oxford", "disabled": true, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }''' // Invalid userEmailAddresses.reader || UNPROCESSABLE_ENTITY | '{"total":1, "errors": [{"message"' + @@ -968,19 +986,19 @@ json { } when: 'requesting a reset link' - params.emailAddress = reader.emailAddress + params.emailAddress = editor.emailAddress controller.sendPasswordResetLink() then: verifyResponse OK and: 'reset token is set' - reader.resetToken + editor.resetToken when: 'user then tries to use reset token' response.reset() params.currentUserSecurityPolicyManager = NoAccessSecurityPolicyManager.instance - params.catalogueUserId = reader.id.toString() + params.catalogueUserId = editor.id.toString() request.method = 'POST' request.json = '{"newPassword": "another", "resetToken":""}' @@ -1008,7 +1026,7 @@ json { findByEmailAddress(_) >> { String em -> CatalogueUser.findByEmailAddress(em) } 0 * generatePasswordResetLink(_, _) 0 * changeUserPassword(_, _, _) - findOrCreateUserFromInterface(_) >> { User u -> + findOrCreateUserFromInterface(_) >> {User u -> if (u instanceof CatalogueUser) return u CatalogueUser catalogueUser = CatalogueUser.get(u.id) ?: CatalogueUser.findByEmailAddress(u.emailAddress) catalogueUser ?: CatalogueUser.fromInterface(u) @@ -1017,8 +1035,8 @@ json { when: 'user then tries to use reset token' request.method = 'POST' - params.catalogueUserId = reader.id.toString() - params.emailAddress = reader.emailAddress + params.catalogueUserId = editor.id.toString() + params.emailAddress = editor.emailAddress request.json = '{"newPassword": "another", "resetToken":""}' params.currentUserSecurityPolicyManager = NoAccessSecurityPolicyManager.instance controller.resetPassword() @@ -1067,20 +1085,20 @@ json { } when: 'requesting a reset link' - params.emailAddress = reader.emailAddress + params.emailAddress = editor.emailAddress controller.sendPasswordResetLink() then: verifyResponse OK and: 'reset token is set' - reader.resetToken + editor.resetToken when: 'user then tries to use reset token' - UUID token = reader.resetToken + UUID token = editor.resetToken response.reset() params.currentUserSecurityPolicyManager = NoAccessSecurityPolicyManager.instance - params.catalogueUserId = reader.id.toString() + params.catalogueUserId = editor.id.toString() request.method = 'POST' request.json = "{\"newPassword\": \"another\", \"resetToken\":\"${token}\"}".toString() controller.resetPassword() @@ -1089,7 +1107,7 @@ json { verifyResponse OK and: - !reader.resetToken + !editor.resetToken } @@ -1133,32 +1151,32 @@ json { } when: 'requesting a reset link' - params.emailAddress = reader.emailAddress + params.emailAddress = editor.emailAddress controller.sendPasswordResetLink() then: 'reset token is set' - reader.resetToken + editor.resetToken when: 'user then logs in using known password' - UUID token = reader.resetToken + UUID token = editor.resetToken response.reset() - params.catalogueUserId = reader.id.toString() - controller.catalogueUserService.setUserLastLoggedIn(reader) + params.catalogueUserId = editor.id.toString() + controller.catalogueUserService.setUserLastLoggedIn(editor) then: 'token is removed' - !reader.resetToken + !editor.resetToken when: 'user then tries to use reset token' response.reset() params.currentUserSecurityPolicyManager = NoAccessSecurityPolicyManager.instance - params.catalogueUserId = reader.id.toString() + params.catalogueUserId = editor.id.toString() request.method = 'POST' request.json = "{\"newPassword\": \"another\", \"resetToken\":\"${token}\"}".toString() controller.resetPassword() then: verifyJsonResponse UNPROCESSABLE_ENTITY, '{"total":1, "errors": [{"message":' + - '"Cannot change password for user [reader@test.com] as old password is not valid"}]}' + '"Cannot change password for user [editor@test.com] as old password is not valid"}]}' } @@ -1223,7 +1241,8 @@ json { "availableActions": ["delete","show","update"], "pending": false, "disabled": false, - "id": "${json-unit.matches:id}" + "id": "${json-unit.matches:id}", + "creationMethod": "Standard" }''' and: editor.tempPassword @@ -1273,6 +1292,7 @@ json { "pending": true, "disabled": false, "id": "${json-unit.matches:id}", + "creationMethod": "Standard", "groups": [{ "id": "${json-unit.matches:id}", "name": "testgroup" diff --git a/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/CatalogueItemFacetFunctionalSpec.groovy b/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/CatalogueItemFacetFunctionalSpec.groovy index 4f29762d5d..72ac6d5337 100644 --- a/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/CatalogueItemFacetFunctionalSpec.groovy +++ b/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/CatalogueItemFacetFunctionalSpec.groovy @@ -55,15 +55,13 @@ abstract class CatalogueItemFacetFunctionalSpec extends Re log.debug('Check and setup test data') folder = new Folder(label: 'Functional Test Folder', createdBy: FUNCTIONAL_TEST) checkAndSave(folder) - testAuthority = new Authority(label: 'Test Authority', url: "https://localhost", createdBy: FUNCTIONAL_TEST) - checkAndSave(testAuthority) + testAuthority = Authority.findByLabel('Test Authority') } @Transactional def cleanupSpec() { log.debug('CleanupSpec CatalogueItemFacetFunctionalSpec') cleanUpResources(Folder) - Authority.findByLabel('Test Authority').delete(flush: true) } String getCopyResourcePath(String copyId) { diff --git a/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/ContainerFacetFunctionalSpec.groovy b/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/ContainerFacetFunctionalSpec.groovy index b47e9a356f..9103928a8e 100644 --- a/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/ContainerFacetFunctionalSpec.groovy +++ b/mdm-testing-framework/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/test/functional/facet/ContainerFacetFunctionalSpec.groovy @@ -17,7 +17,6 @@ */ package uk.ac.ox.softeng.maurodatamapper.test.functional.facet - import uk.ac.ox.softeng.maurodatamapper.core.authority.Authority import uk.ac.ox.softeng.maurodatamapper.core.container.Folder import uk.ac.ox.softeng.maurodatamapper.test.functional.ResourceFunctionalSpec @@ -28,8 +27,6 @@ import groovy.util.logging.Slf4j import org.grails.datastore.gorm.GormEntity import spock.lang.Shared -import static uk.ac.ox.softeng.maurodatamapper.core.bootstrap.StandardEmailAddress.FUNCTIONAL_TEST - @Slf4j abstract class ContainerFacetFunctionalSpec extends ResourceFunctionalSpec { @@ -51,7 +48,7 @@ abstract class ContainerFacetFunctionalSpec extends Resour @Transactional def checkAndSetupFolderAndAuthority() { log.debug('Check and setup test data') - testAuthority = new Authority(label: 'Test Authority', url: "https://localhost", createdBy: FUNCTIONAL_TEST) + testAuthority = Authority.findByLabel('Test Authority') checkAndSave(testAuthority) } @@ -59,6 +56,5 @@ abstract class ContainerFacetFunctionalSpec extends Resour def cleanupSpec() { log.debug('CleanupSpec ContainerFacetFunctionalSpec') cleanUpResources(Folder) - Authority.findByLabel('Test Authority').delete(flush: true) } } \ No newline at end of file diff --git a/mdm-testing-functional/grails-app/conf/application.yml b/mdm-testing-functional/grails-app/conf/application.yml index 727fbc4513..58026be61a 100644 --- a/mdm-testing-functional/grails-app/conf/application.yml +++ b/mdm-testing-functional/grails-app/conf/application.yml @@ -113,7 +113,7 @@ grails: # The following are the defaults allowedOrigins: ['*'] allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD'] - allowedHeaders: ['origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control'] + allowedHeaders: [ 'origin', 'content-type', 'accept', 'authorization', 'pragma', 'cache-control' ] #exposedHeaders: null #maxAge: 1800 #allowCredentials: true @@ -123,6 +123,16 @@ grails: prettyPrint: false autoIndent: false autoNewLine: false + databinding: + dateFormats: + - "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SSXXX" + - "yyyy-MM-dd'T'HH:mm:ss.SXXX" + - "yyyy-MM-dd'T'HH:mm:ssXXX" + - 'yyyy-MM-dd HH:mm:ss.S' + - "yyyy-MM-dd'T'HH:mm:ss'Z'" + - "yyyy-MM-dd HH:mm:ss.S z" + - "yyyy-MM-dd" --- hibernate: search: diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/core/container/VersionedFolderFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/core/container/VersionedFolderFunctionalSpec.groovy index 02bfd2036d..f52fbda78f 100644 --- a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/core/container/VersionedFolderFunctionalSpec.groovy +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/core/container/VersionedFolderFunctionalSpec.groovy @@ -29,6 +29,7 @@ import uk.ac.ox.softeng.maurodatamapper.util.VersionChangeType import grails.gorm.transactions.Transactional import grails.testing.mixin.integration.Integration +import grails.web.mime.MimeType import groovy.util.logging.Slf4j import io.micronaut.http.HttpResponse import io.micronaut.http.HttpStatus @@ -522,6 +523,107 @@ class VersionedFolderFunctionalSpec extends UserAccessAndPermissionChangingFunct removeValidIdObject(folderId) } + void 'I01 : Test importing non-finalised model into a top level VF (as admin)'() { + given: + String id = getValidId() + + when: + loginAdmin() + POST('dataModels/import/uk.ac.ox.softeng.maurodatamapper.datamodel.provider.importer/DataModelJsonImporterService/2.0', [ + finalised : false, + modelName : 'Functional Test Import', + folderId : id, + importFile: [ + fileName : 'FT Import', + fileType : MimeType.JSON_API.name, + fileContents: '''{ + "dataModel": { + "id": "d8023de6-5329-4b8b-8a1b-27c2abeaffcd", + "label": "Import Model", + "lastUpdated": "2021-02-11T17:43:53.2Z", + "author": "Import Author", + "organisation": "Import Organisation", + "documentationVersion": "1.0.0", + "finalised": false, + "authority": { + "id": "82429f5a-c3f9-45f2-8ed5-0426f5b0030d", + "url": "http://localhost", + "label": "Mauro Data Mapper" + } + }, + "exportMetadata": { + "exportedBy": "Admin User", + "exportedOn": "2021-02-14T18:32:37.522Z", + "exporter": { + "namespace": "uk.ac.ox.softeng.maurodatamapper.datamodel.provider.exporter", + "name": "DataModelJsonExporterService", + "version": "2.0" + } + } + }'''.bytes.toList() + ] + ], MAP_ARG, true) + + then: + verifyResponse CREATED, response + + cleanup: + cleanupIds(id) + + } + + void 'I02 : Test importing non-finalised model into a sub level VF (as admin)'() { + given: + loginEditor() + POST("folders/${testFolderId}/versionedFolders", validJson, MAP_ARG, true) + verifyResponse CREATED, response + String id = response.body().id + + when: + loginAdmin() + POST('dataModels/import/uk.ac.ox.softeng.maurodatamapper.datamodel.provider.importer/DataModelJsonImporterService/2.0', [ + finalised : false, + modelName : 'Functional Test Import', + folderId : id, + importFile: [ + fileName : 'FT Import', + fileType : MimeType.JSON_API.name, + fileContents: '''{ + "dataModel": { + "id": "d8023de6-5329-4b8b-8a1b-27c2abeaffcd", + "label": "Import Model", + "lastUpdated": "2021-02-11T17:43:53.2Z", + "author": "Import Author", + "organisation": "Import Organisation", + "documentationVersion": "1.0.0", + "finalised": false, + "authority": { + "id": "82429f5a-c3f9-45f2-8ed5-0426f5b0030d", + "url": "http://localhost", + "label": "Mauro Data Mapper" + } + }, + "exportMetadata": { + "exportedBy": "Admin User", + "exportedOn": "2021-02-14T18:32:37.522Z", + "exporter": { + "namespace": "uk.ac.ox.softeng.maurodatamapper.datamodel.provider.exporter", + "name": "DataModelJsonExporterService", + "version": "2.0" + } + } + }'''.bytes.toList() + ] + ], MAP_ARG, true) + + then: + verifyResponse CREATED, response + + cleanup: + cleanupIds(id) + + } + void 'F01 : Test finalising Model (as reader)'() { given: Map data = getValidIdWithContent() diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedCatalogueFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedCatalogueFunctionalSpec.groovy index 64f78d922a..1da467b82a 100644 --- a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedCatalogueFunctionalSpec.groovy +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedCatalogueFunctionalSpec.groovy @@ -588,11 +588,11 @@ class SubscribedCatalogueFunctionalSpec extends FunctionalSpec { } /** - * Test the availableModels endpoint. This would be on a remote host, but in this functional test + * Test the publishedModels endpoint. This would be on a remote host, but in this functional test * we use the localhost. Test setup and execution is as follows: * 1. Login as Admin and create an API Key for Admin * 2. Subscribe to the local catalogue (in real life this would be remote), specifying the API key created above - * 3. Get the local /availableModels endpoint. In real life this would connect to /api/feeds/all on the remote, + * 3. Get the local /publishedModels endpoint. In real life this would connect to /api/feeds/all on the remote, * but here we use the local. * 4. Cleanup */ @@ -628,7 +628,7 @@ class SubscribedCatalogueFunctionalSpec extends FunctionalSpec { String subscribedCatalogueId = responseBody().id when: - GET("${subscribedCatalogueId}/availableModels", STRING_ARG) + GET("${subscribedCatalogueId}/publishedModels", STRING_ARG) then: verifyJsonResponse OK, ''' @@ -636,16 +636,21 @@ class SubscribedCatalogueFunctionalSpec extends FunctionalSpec { "count": 2, "items": [ { - "modelId": "${json-unit.matches:id}", - "label": "Simple Test CodeSet 1.0.0", - "description": null, - "modelType": "CodeSet" + "modelId": null, + "title": "Simple Test CodeSet 1.0.0", + "modelType": "CodeSet", + "lastUpdated": "${json-unit.matches:offsetDateTime}", + "dateCreated": "${json-unit.matches:offsetDateTime}", + "datePublished": "${json-unit.matches:offsetDateTime}", + "author": "Test Bootstrap" }, { - "modelId": "${json-unit.matches:id}", - "label": "Finalised Example Test DataModel 1.0.0", - "description": null, - "modelType": "DataModel" + "modelId": null, + "title": "Finalised Example Test DataModel 1.0.0", + "modelType": "DataModel", + "lastUpdated": "${json-unit.matches:offsetDateTime}", + "dateCreated": "${json-unit.matches:offsetDateTime}", + "datePublished": "${json-unit.matches:offsetDateTime}" } ] } diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedModelFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedModelFunctionalSpec.groovy index 4e95209345..ca2aefb8c9 100644 --- a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedModelFunctionalSpec.groovy +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/SubscribedModelFunctionalSpec.groovy @@ -18,7 +18,10 @@ package uk.ac.ox.softeng.maurodatamapper.testing.functional.federation import uk.ac.ox.softeng.maurodatamapper.core.container.Folder +import uk.ac.ox.softeng.maurodatamapper.datamodel.DataModel +import uk.ac.ox.softeng.maurodatamapper.datamodel.bootstrap.BootstrapModels import uk.ac.ox.softeng.maurodatamapper.federation.SubscribedCatalogue +import uk.ac.ox.softeng.maurodatamapper.security.authentication.ApiKey import uk.ac.ox.softeng.maurodatamapper.security.role.SecurableResourceGroupRole import uk.ac.ox.softeng.maurodatamapper.testing.functional.FunctionalSpec import uk.ac.ox.softeng.maurodatamapper.util.Utils @@ -30,7 +33,9 @@ import groovy.util.logging.Slf4j import spock.lang.Shared import java.nio.file.Path +import java.time.LocalDate +import static uk.ac.ox.softeng.maurodatamapper.core.bootstrap.StandardEmailAddress.ADMIN import static uk.ac.ox.softeng.maurodatamapper.core.bootstrap.StandardEmailAddress.FUNCTIONAL_TEST import static io.micronaut.http.HttpStatus.CREATED @@ -47,31 +52,40 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { @Shared UUID subscribedCatalogueId + @Shared + UUID adminApiKey + @OnceBefore @Transactional def checkAndSetupData() { log.debug('Check and setup test data for SubscribedModelFunctionalSpec') sessionFactory.currentSession.flush() - subscribedCatalogueId = new SubscribedCatalogue(url: 'http://functional-test.example.com', - apiKey: UUID.randomUUID(), + adminApiKey = new ApiKey(catalogueUser: getUserByEmailAddress(ADMIN), + name: "Functional Test", + expiryDate: LocalDate.now().plusDays(5), + createdBy: FUNCTIONAL_TEST).save(flush: true).id + + + subscribedCatalogueId = new SubscribedCatalogue(url: "http://localhost:$serverPort/".toString(), + apiKey: adminApiKey, label: 'Functional Test Label', description: 'Functional Test Description', refreshPeriod: 7, createdBy: FUNCTIONAL_TEST).save(flush: true).id assert subscribedCatalogueId - + } @Transactional String getFolderId() { Folder.findByLabel('Functional Test Folder').id.toString() - } + } def cleanupSpec() { log.debug('CleanupSpec SubscribedModelFunctionalSpec') - cleanUpResources(SubscribedCatalogue) - } + cleanUpResources(SubscribedCatalogue, ApiKey) + } @Shared Path resourcesPath @@ -83,7 +97,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { String getValidId() { loginAdmin() - POST('', validJson) + POST('?federate=false', validJson) verifyResponse CREATED, response String id = response.body().id logout() @@ -103,12 +117,12 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { log.debug('Cleaning up {} roles for ids {}', SecurableResourceGroupRole.count(), ids) SecurableResourceGroupRole.bySecurableResourceIds(ids.collect { Utils.toUuid(it) }).deleteAll() sessionFactory.currentSession.flush() - } + } Map getValidJson() { [ - subscribedModelId: '67421316-66a5-4830-9156-b1ba77bba5d1', - folderId: getFolderId(), + subscribedModelId : '67421316-66a5-4830-9156-b1ba77bba5d1', + folderId : getFolderId(), subscribedModelType: 'DataModel' ] } @@ -125,11 +139,12 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { subscribedModelId: '67421316-66a5-4830-9156-b1ba77bba5d1', folderId: getFolderId() ] - } + } /* * Logged in as editor testing */ + void 'E02 : Test the show and index action correctly renders an instance for set user (as editor)'() { given: String id = getValidId() @@ -145,7 +160,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { GET('') then: - verifyResponse OK, response + verifyResponse OK, response cleanup: removeValidIdObject(id) @@ -155,6 +170,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { /* * Logged out testing */ + void 'L02 : Test the show and index action does not render an instance for set user (not logged in)'() { given: String id = getValidId() @@ -169,7 +185,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { GET('') then: - verifyResponse NOT_FOUND, response + verifyResponse NOT_FOUND, response cleanup: removeValidIdObject(id) @@ -194,7 +210,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { GET('') then: - verifyResponse OK, response + verifyResponse OK, response cleanup: removeValidIdObject(id) @@ -219,16 +235,17 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { GET('') then: - verifyResponse OK, response + verifyResponse OK, response cleanup: removeValidIdObject(id) cleanUpRoles(id) } - /* - * Logged in as admin testing - */ + /* + * Logged in as admin testing + */ + void 'A02 : Test the show action correctly renders an instance for set user (as admin)'() { given: String id = getValidId() @@ -245,10 +262,11 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanUpRoles(id) } - /* - * Logged in as editor testing - */ - void 'E03a : Test the save action is forbidden (as editor)'() { + /* + * Logged in as editor testing + */ + + void 'E03a : Test the save action is forbidden (as editor)'() { given: loginEditor() @@ -271,7 +289,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse FORBIDDEN, response } - void 'E03b : Test the save action is forbidden when using PUT (as editor)'() { + void 'E03b : Test the save action is forbidden when using PUT (as editor)'() { given: String id = getValidId() loginEditor() @@ -289,9 +307,9 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse FORBIDDEN, response cleanup: - removeValidIdObject(id) - cleanUpRoles(id) - } + removeValidIdObject(id) + cleanUpRoles(id) + } void 'E04 : Test the delete action is forbidden (as editor)'() { given: @@ -305,15 +323,15 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse FORBIDDEN, response cleanup: - removeValidIdObject(id) - cleanUpRoles(id) + removeValidIdObject(id) + cleanUpRoles(id) } /* * Logged out testing */ - void 'L03a : Test the save action is not found (as not logged in)'() { + void 'L03a : Test the save action is not found (as not logged in)'() { given: when: 'The save action is executed with no content' @@ -335,7 +353,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse NOT_FOUND, response } - void 'L03b : Test the save action is not found when using PUT (as not logged in)'() { + void 'L03b : Test the save action is not found when using PUT (as not logged in)'() { given: String id = getValidId() @@ -352,9 +370,9 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse NOT_FOUND, response cleanup: - removeValidIdObject(id) - cleanUpRoles(id) - } + removeValidIdObject(id) + cleanUpRoles(id) + } void 'L04 : Test the delete action is not found (as not logged in)'() { given: @@ -367,15 +385,15 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse NOT_FOUND, response cleanup: - removeValidIdObject(id) - cleanUpRoles(id) + removeValidIdObject(id) + cleanUpRoles(id) } /** * Testing when logged in as a no access/authenticated user */ - void 'N03a : Test the save action is forbidden (as authenticated)'() { + void 'N03a : Test the save action is forbidden (as authenticated)'() { given: loginAuthenticated() @@ -398,7 +416,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse FORBIDDEN, response } - void 'N03b : Test the save action is forbidden when using PUT (as authenticated)'() { + void 'N03b : Test the save action is forbidden when using PUT (as authenticated)'() { given: String id = getValidId() loginAuthenticated() @@ -417,8 +435,8 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanup: removeValidIdObject(id) - cleanUpRoles(id) - } + cleanUpRoles(id) + } void 'N04 : Test the delete action is forbidden (as authenticated)'() { given: @@ -433,14 +451,14 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanup: removeValidIdObject(id) - cleanUpRoles(id) + cleanUpRoles(id) } /** * Testing when logged in as a reader only user */ - void 'R03a : Test the save action is forbidden (as reader)'() { + void 'R03a : Test the save action is forbidden (as reader)'() { given: loginReader() @@ -463,7 +481,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse FORBIDDEN, response } - void 'R03b : Test the save action is forbidden when using PUT (as reader)'() { + void 'R03b : Test the save action is forbidden when using PUT (as reader)'() { given: String id = getValidId() loginReader() @@ -483,7 +501,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanup: removeValidIdObject(id) cleanUpRoles(id) - } + } void 'R04 : Test the delete action is forbidden (as reader)'() { given: @@ -498,14 +516,15 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanup: removeValidIdObject(id) - cleanUpRoles(id) + cleanUpRoles(id) } /* * Logged in as admin testing * This proves that admin users can mess with items created by other users */ - void 'A03a : Test the save action is ok (as admin)'() { + + void 'A03a : Test the save action is ok (as admin)'() { given: loginAdmin() @@ -522,7 +541,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse UNPROCESSABLE_ENTITY, response when: 'The save action is executed with valid data' - POST('', validJson) + POST('?federate=false', validJson) then: 'The response is correct' verifyResponse CREATED, response @@ -533,7 +552,7 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanUpRoles(id) } - void 'A03b : Test the save action is OK when using PUT (as admin)'() { + void 'A03b : Test the save action is OK when using PUT (as admin)'() { given: String id = getValidId() loginAdmin() @@ -552,9 +571,9 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { cleanup: removeValidIdObject(id) - cleanUpRoles(id) + cleanUpRoles(id) } - + void 'A04 : Test the delete action is ok (as admin)'() { given: @@ -568,6 +587,37 @@ class SubscribedModelFunctionalSpec extends FunctionalSpec { verifyResponse NO_CONTENT, response cleanup: - cleanUpRoles(id) - } + cleanUpRoles(id) + } + + void 'A05 : Test the save action with attempted federation (as admin)'() { + given: + loginAdmin() + + // when: 'The save action is executed with non-existent published model id' + // POST('?federate=true', validJson) + // + // then: 'The response is unprocessable as no export endpoint' + // verifyResponse UNPROCESSABLE_ENTITY, response + // responseBody().errors.first().message == 'Could not federate SubscribedModel into local Catalogue due to ' + + // "[Requested endpoint could not be found http://localhost:$serverPort/api/dataModels/67421316-66a5-4830-9156-b1ba77bba5d1/export]" + + when: 'The save action is executed with existing published model id' + POST('?federate=true', [ + subscribedModelId : getPublishedDataModelId(), + folderId : getFolderId(), + subscribedModelType: 'DataModel' + ]) + + then: 'The response is unprocessable as subscribed/federated model has same authority and label as model already in storage' + verifyResponse UNPROCESSABLE_ENTITY, response + responseBody().errors.first().message == 'Model from authority [Mauro Data Mapper@http://localhost] with label [Finalised Example Test DataModel] ' + + 'and version [1.0.0] already exists in catalogue' + + } + + @Transactional + String getPublishedDataModelId() { + DataModel.findByLabel(BootstrapModels.FINALISED_EXAMPLE_DATAMODEL_NAME).id.toString() + } } diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/FeedFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/atom/FeedFunctionalSpec.groovy similarity index 98% rename from mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/FeedFunctionalSpec.groovy rename to mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/atom/FeedFunctionalSpec.groovy index 7216cac843..091bb7a2db 100644 --- a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/FeedFunctionalSpec.groovy +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/atom/FeedFunctionalSpec.groovy @@ -15,7 +15,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package uk.ac.ox.softeng.maurodatamapper.testing.functional.federation +package uk.ac.ox.softeng.maurodatamapper.testing.functional.federation.atom import uk.ac.ox.softeng.maurodatamapper.core.admin.ApiPropertyEnum import uk.ac.ox.softeng.maurodatamapper.testing.functional.FunctionalSpec @@ -35,7 +35,7 @@ import static io.micronaut.http.HttpStatus.OK * * * - * @see uk.ac.ox.softeng.maurodatamapper.federation.FeedController + * @see uk.ac.ox.softeng.maurodatamapper.federation.atom.FeedController */ @Integration @Slf4j diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/publish/PublishFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/publish/PublishFunctionalSpec.groovy new file mode 100644 index 0000000000..1393f7cf88 --- /dev/null +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/federation/publish/PublishFunctionalSpec.groovy @@ -0,0 +1,88 @@ +/* + * Copyright 2020 University of Oxford and Health and Social Care Information Centre, also known as NHS Digital + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package uk.ac.ox.softeng.maurodatamapper.testing.functional.federation.publish + + +import uk.ac.ox.softeng.maurodatamapper.testing.functional.FunctionalSpec + +import grails.testing.mixin.integration.Integration +import groovy.util.logging.Slf4j + +import static io.micronaut.http.HttpStatus.OK + +/** + *
+ * Controller: publish
+ *  |   GET   | /api/published/models       | Action: index
+ * 
+ * + * + * @see uk.ac.ox.softeng.maurodatamapper.federation.publish.PublishController + */ +@Integration +@Slf4j +class PublishFunctionalSpec extends FunctionalSpec { + + @Override + String getResourcePath() { + 'published' + } + + void 'Get published models when not logged in'() { + + when: + GET('models') + + then: "The response is OK with no entries" + verifyResponse(OK, response) + responseBody().authority.label == 'Mauro Data Mapper' + responseBody().authority.url == 'http://localhost' + responseBody().lastUpdated + responseBody().publishedModels.isEmpty() + } + + void 'Get published models when logged in as reader'() { + + given: + loginReader() + + when: + GET('models') + + then: + verifyResponse(OK, response) + responseBody().authority.label == 'Mauro Data Mapper' + responseBody().authority.url == 'http://localhost' + responseBody().lastUpdated + responseBody().publishedModels.size() == 2 + + + and: + verifyEntry(responseBody().publishedModels.find { it.title == 'Simple Test CodeSet 1.0.0' }, 'CodeSet') + verifyEntry(responseBody().publishedModels.find { it.title == 'Finalised Example Test DataModel 1.0.0' }, 'DataModel') + } + + private void verifyEntry(Map publishedModel, String modelType) { + assert publishedModel + assert publishedModel.modelId.toString() ==~ /\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/ + assert publishedModel.modelType == modelType + assert publishedModel.datePublished + assert publishedModel.lastUpdated + assert publishedModel.dateCreated + } +} diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/referencedata/ReferenceDataModelFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/referencedata/ReferenceDataModelFunctionalSpec.groovy index f57daec788..4d3e4c5e69 100644 --- a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/referencedata/ReferenceDataModelFunctionalSpec.groovy +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/referencedata/ReferenceDataModelFunctionalSpec.groovy @@ -93,7 +93,7 @@ class ReferenceDataModelFunctionalSpec extends ModelUserAccessPermissionChanging Path testFilePath = resourcesPath.resolve("${filename}").toAbsolutePath() assert Files.exists(testFilePath) Files.readAllBytes(testFilePath) - } + } @Transactional String getTestFolderId() { @@ -769,7 +769,7 @@ class ReferenceDataModelFunctionalSpec extends ModelUserAccessPermissionChanging verifyJsonResponse OK, new String(loadTestFile('expectedGetValuesMax.json')) when: - GET("${id}/referenceDataValues?asRows=true", STRING_ARG) + GET("${id}/referenceDataValues?asRows=true", STRING_ARG) then: verifyJsonResponse OK, new String(loadTestFile('expectedGetValuesAsRows.json')) @@ -778,7 +778,7 @@ class ReferenceDataModelFunctionalSpec extends ModelUserAccessPermissionChanging GET("${id}/referenceDataValues?asRows=true&max=2", STRING_ARG) then: - verifyJsonResponse OK, new String(loadTestFile('expectedGetValuesAsRowsMax.json')) + verifyJsonResponse OK, new String(loadTestFile('expectedGetValuesAsRowsMax.json')) when: GET("${id}/referenceDataValues/search?search=Row6Value2", STRING_ARG) @@ -790,10 +790,10 @@ class ReferenceDataModelFunctionalSpec extends ModelUserAccessPermissionChanging GET("${id}/referenceDataValues/search?search=Row6Value2&asRows=true", STRING_ARG) then: - verifyJsonResponse OK, new String(loadTestFile('expectedSearchValuesRow6AsRows.json')) + verifyJsonResponse OK, new String(loadTestFile('expectedSearchValuesRow6AsRows.json')) cleanup: logout() removeValidIdObjectUsingTransaction(id) - } + } } \ No newline at end of file diff --git a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/security/CatalogueUserFunctionalSpec.groovy b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/security/CatalogueUserFunctionalSpec.groovy index 8bb2c28f66..366a94baa2 100644 --- a/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/security/CatalogueUserFunctionalSpec.groovy +++ b/mdm-testing-functional/src/integration-test/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/security/CatalogueUserFunctionalSpec.groovy @@ -125,6 +125,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "lastName": "new user", "needsToResetPassword": true, "emailAddress": "user@functional-test.com", + "creationMethod": "Standard", "availableActions": ["disable","show", "update"], "disabled": false, "id": "${json-unit.matches:id}", @@ -140,6 +141,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "disabled": false, "id": "${json-unit.matches:id}", "pending": true, + "creationMethod": "Standard", "createdBy": "user@functional-test.com" }''' @@ -173,6 +175,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": false, "disabled": false, "createdBy": "admin@maurodatamapper.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -197,6 +200,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -213,6 +217,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -233,6 +238,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": false, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -253,6 +259,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": false, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -274,6 +281,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": true, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -291,6 +299,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -313,6 +322,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "disabled": false, "needsToResetPassword": true, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -333,6 +343,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": false, "disabled": false, "createdBy": "unlogged_user@mdm-core.com", + "creationMethod": "Standard", "availableActions": [ "update", "disable", @@ -487,6 +498,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": false, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["disable","show", "update"], "lastLogin": "${json-unit.matches:offsetDateTime}", "groups": [ @@ -510,6 +522,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": false, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": ["disable","show", "update"], "lastLogin": "${json-unit.matches:offsetDateTime}", "groups": [ @@ -851,6 +864,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": true, "disabled": false, "createdBy": "functional-test@test.com", + "creationMethod": "Standard", "availableActions": [ "update","disable","show"], "organisation": "Oxford", "jobTitle": "tester" @@ -863,6 +877,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": true, "disabled": true, "createdBy": "user@functional-test.com", + "creationMethod": "Standard", "availableActions": [ "update","disable","show"] } ] @@ -890,6 +905,7 @@ class CatalogueUserFunctionalSpec extends FunctionalSpec { "pending": true, "disabled": true, "createdBy": "user@functional-test.com", + "creationMethod": "Standard", "availableActions": [ "update","disable","show"] } ] diff --git a/mdm-testing-functional/src/integration-test/resources/referencedata/expectedExport.json b/mdm-testing-functional/src/integration-test/resources/referencedata/expectedExport.json index 6d97ed2155..52a31ad152 100644 --- a/mdm-testing-functional/src/integration-test/resources/referencedata/expectedExport.json +++ b/mdm-testing-functional/src/integration-test/resources/referencedata/expectedExport.json @@ -6,7 +6,7 @@ "type": "ReferenceDataModel", "documentationVersion": "1.0.0", "finalised": true, - "dateFinalised": null, + "dateFinalised": "${json-unit.matches:offsetDateTime}", "modelVersion": "1.0.0", "authority": { "id": "${json-unit.matches:id}", diff --git a/mdm-testing-functional/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/ModelUserAccessPermissionChangingAndVersioningFunctionalSpec.groovy b/mdm-testing-functional/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/ModelUserAccessPermissionChangingAndVersioningFunctionalSpec.groovy index 24bfa98a38..fb81531f79 100644 --- a/mdm-testing-functional/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/ModelUserAccessPermissionChangingAndVersioningFunctionalSpec.groovy +++ b/mdm-testing-functional/src/main/groovy/uk/ac/ox/softeng/maurodatamapper/testing/functional/ModelUserAccessPermissionChangingAndVersioningFunctionalSpec.groovy @@ -892,8 +892,8 @@ abstract class ModelUserAccessPermissionChangingAndVersioningFunctionalSpec exte then: verifyResponse OK, response responseBody().count == 2 - responseBody().items.each { it.id in [newBranchId, latestDraftId] } - responseBody().items.each { it.label == validJson.label } + responseBody().items.each {it.id in [newBranchId, latestDraftId]} + responseBody().items.each {it.label == validJson.label} cleanup: removeValidIdObjectUsingTransaction(id) @@ -1093,7 +1093,7 @@ abstract class ModelUserAccessPermissionChangingAndVersioningFunctionalSpec exte } void cleanupModelVersionTree(Map data) { - data.each { k, v -> + data.each {k, v -> removeValidIdObjectUsingTransaction(v) } cleanUpRoles(data.values()) @@ -1367,6 +1367,105 @@ abstract class ModelUserAccessPermissionChangingAndVersioningFunctionalSpec exte cleanupModelVersionTree(data) } + void 'E28d : Test getting simple versionTreeModel for merge shows only branches and not the selected model'() { + given: + Map data = buildModelVersionTree() + loginEditor() + String expectedJson = """[ + { + "id": "${data.newBranch}", + "branch": "newBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "newBranch (V1.0.0)" + }, + { + "id": "${data.testBranch}", + "branch": "testBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "testBranch (V3.0.0)" + }, + { + "id": "${data.main}", + "branch": "main", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "main (V5.0.0)" + }, + { + "id": "${data.anotherBranch}", + "branch": "anotherBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "anotherBranch (V5.0.0)" + } +]""" + + when: 'getting the tree' + GET("${data.interestingBranch}/simpleModelVersionTree?forMerge=true", STRING_ARG) + + then: + verifyResponse OK, jsonCapableResponse + verifyJson(expectedJson, jsonCapableResponse.body(), false, true) + + cleanup: + cleanupModelVersionTree(data) + } + + void 'E28e : Test getting simple versionTreeModel with branches only shows only branches'() { + given: + Map data = buildModelVersionTree() + loginEditor() + String expectedJson = """[ + { + "id": "${data.newBranch}", + "branch": "newBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "newBranch (V1.0.0)" + }, + { + "id": "${data.testBranch}", + "branch": "testBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "testBranch (V3.0.0)" + }, + { + "id": "${data.main}", + "branch": "main", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "main (V5.0.0)" + }, + { + "id": "${data.anotherBranch}", + "branch": "anotherBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "anotherBranch (V5.0.0)" + }, + { + "id": "${data.interestingBranch}", + "branch": "interestingBranch", + "modelVersion": null, + "documentationVersion": "1.0.0", + "displayName": "interestingBranch (V5.0.0)" + } +]""" + + when: 'getting the tree' + GET("${data.interestingBranch}/simpleModelVersionTree?branchesOnly=true", STRING_ARG) + + then: + verifyResponse OK, jsonCapableResponse + verifyJson(expectedJson, jsonCapableResponse.body(), false, true) + + cleanup: + cleanupModelVersionTree(data) + } + void 'R29a : Test available actions inside a VersionedFolder (as reader)'() { given: loginEditor()