diff --git a/core/src/main/java/org/fao/geonet/kernel/AbstractSchematronValidator.java b/core/src/main/java/org/fao/geonet/kernel/AbstractSchematronValidator.java index 86454a7a0f2b..3f8ed268f3a5 100644 --- a/core/src/main/java/org/fao/geonet/kernel/AbstractSchematronValidator.java +++ b/core/src/main/java/org/fao/geonet/kernel/AbstractSchematronValidator.java @@ -70,6 +70,7 @@ protected void runSchematron(String lang, Path schemaDir, List applicableSchematron = getApplicableSchematronList(md, metadataSchema, groupOwnerId); for (ApplicableSchematron applicable : applicableSchematron) { + // Pass -1 as metadataId for external validation since the metadata is not in the database. + // Schematron rules can reference the metadataId parameter, but attempting to look up metadata + // using -1 will return empty results. runSchematron(lang, schemaDir, validations, schemaTronXmlOut, -1, md, applicable); } } catch (Throwable e) { diff --git a/core/src/test/java/org/fao/geonet/kernel/AbstractSchematronValidatorTest.java b/core/src/test/java/org/fao/geonet/kernel/AbstractSchematronValidatorTest.java index c7242793b602..15519e870ca9 100644 --- a/core/src/test/java/org/fao/geonet/kernel/AbstractSchematronValidatorTest.java +++ b/core/src/test/java/org/fao/geonet/kernel/AbstractSchematronValidatorTest.java @@ -37,6 +37,7 @@ import org.fao.geonet.exceptions.ResourceNotFoundEx; import org.fao.geonet.utils.Xml; import org.jdom.Element; +import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -44,8 +45,10 @@ import org.springframework.context.ConfigurableApplicationContext; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.ResourceBundle; import static org.junit.Assert.assertEquals; @@ -127,4 +130,51 @@ public void testRunSchematronLocalizedExceptionHandling() { assertEquals(MetadataValidationStatus.NEVER_CALCULATED, validations.get(0).getStatus()); } } + + @Test + public void testRunSchematronPassesMetadataIdParam() throws Exception { + ConfigurableApplicationContext mockContext = Mockito.mock(ConfigurableApplicationContext.class); + try (MockedStatic mockedHolder = Mockito.mockStatic(ApplicationContextHolder.class); + MockedStatic mockedXml = Mockito.mockStatic(Xml.class)) { + + mockedHolder.when(ApplicationContextHolder::get).thenReturn(mockContext); + ThesaurusManager mockThesaurusManager = Mockito.mock(ThesaurusManager.class); + Mockito.when(mockContext.getBean(ThesaurusManager.class)).thenReturn(mockThesaurusManager); + + Path thesaurusDir = Paths.get("/thesaurus"); + Mockito.when(mockThesaurusManager.getThesauriDirectory()).thenReturn(thesaurusDir); + + // Capture the params passed to Xml.transform + @SuppressWarnings("unchecked") + ArgumentCaptor> paramsCaptor = ArgumentCaptor.forClass(Map.class); + mockedXml.when(() -> Xml.transform(Mockito.any(), Mockito.any(Path.class), paramsCaptor.capture())) + .thenReturn(new Element("output")); + + AbstractSchematronValidator validator = new AbstractSchematronValidator(); + int expectedMetadataId = 42; + Path schemaDir = Mockito.mock(Path.class); + Path schematronFile = Mockito.mock(Path.class); + Mockito.when(schemaDir.resolve(Mockito.anyString())).thenReturn(schematronFile); + Mockito.when(schematronFile.resolve(Mockito.anyString())).thenReturn(schematronFile); + + List validations = new ArrayList<>(); + Element schemaTronXmlOut = new Element("schemaTronXmlOut"); + Element md = new Element("metadata"); + ApplicableSchematron applicable = Mockito.mock(ApplicableSchematron.class); + + Schematron schematron = new Schematron(); + schematron.setSchemaName("testSchemaName"); + schematron.setFile("schematron-rules-test.xsl"); + + Mockito.when(applicable.getSchematron()).thenReturn(schematron); + Mockito.when(applicable.getRequirement()).thenReturn(SchematronRequirement.REQUIRED); + + validator.runSchematron("eng", schemaDir, validations, schemaTronXmlOut, expectedMetadataId, md, applicable); + + Map capturedParams = paramsCaptor.getValue(); + assertNotNull("params map should not be null", capturedParams); + assertEquals("metadataId should be passed to the schematron XSL transform", + expectedMetadataId, capturedParams.get("metadataId")); + } + } } diff --git a/docs/manual/docs/customizing-application/implementing-a-schema-plugin.md b/docs/manual/docs/customizing-application/implementing-a-schema-plugin.md index ba14664fa6bb..3023734c47ca 100644 --- a/docs/manual/docs/customizing-application/implementing-a-schema-plugin.md +++ b/docs/manual/docs/customizing-application/implementing-a-schema-plugin.md @@ -1220,6 +1220,17 @@ As for most of GeoNetwork, the output of this rule can be localized to different ``` +##### Available parameters in schematron rules + +When GeoNetwork executes a compiled schematron, the following parameters are automatically passed to the XSL and can be referenced inside `` blocks using `$paramName`: + +| Parameter | Type | Description | +|-----------------|---------|-----------------------------------------------------------------------------| +| `lang` | string | The two or three letter language code of the current user session (e.g. `eng`). Use this to return localised messages. | +| `rule` | string | The file name of the compiled schematron XSL (e.g. `schematron-rules-iso.xsl`). | +| `thesaurusDir` | string | Absolute path to the GeoNetwork thesaurus directory. Useful for rules that need to validate against controlled vocabularies. | +| `metadataId` | integer | The internal database identifier of the metadata record being validated. Useful for rules that need to look up related information. **Note:** When validating external metadata (metadata not saved to the database), this parameter is set to `-1`. Attempts to query the database using this value will return empty results. | + Procedure for adding schematron rules, working within the schematrons directory: 1. Place your schematron rules in 'rules'. Naming convention is 'schematron-rules-.sch' eg. `schematron-rules-iso-mcp.sch`. Place localized strings for the rule assertions into 'rules/loc/'. diff --git a/web/src/main/webapp/WEB-INF/classes/schematron/iso_svrl_for_xslt2.xsl b/web/src/main/webapp/WEB-INF/classes/schematron/iso_svrl_for_xslt2.xsl index 5ddd944219da..34d8280ac2ba 100644 --- a/web/src/main/webapp/WEB-INF/classes/schematron/iso_svrl_for_xslt2.xsl +++ b/web/src/main/webapp/WEB-INF/classes/schematron/iso_svrl_for_xslt2.xsl @@ -210,6 +210,7 @@ + diff --git a/web/src/test/java/org/fao/geonet/kernel/schema/AbstractSchematronTest.java b/web/src/test/java/org/fao/geonet/kernel/schema/AbstractSchematronTest.java index e44e5a030897..372020fd5f92 100644 --- a/web/src/test/java/org/fao/geonet/kernel/schema/AbstractSchematronTest.java +++ b/web/src/test/java/org/fao/geonet/kernel/schema/AbstractSchematronTest.java @@ -115,6 +115,7 @@ protected Pair compileSchematron(Path sourceFile) { params.put("lang", "eng"); params.put("thesaurusDir", THESAURUS_DIR.toAbsolutePath().toString().replace("\\", "/")); params.put("rule", ruleName + ".xsl"); + params.put("metadataId", "0"); Path outputFile = temporaryFolder.getRoot().toPath().resolve("path/requiredtoFind/utilsfile/" + ruleName + ".xsl"); Files.createDirectories(outputFile.getParent());