Skip to content

Commit 10c200d

Browse files
committed
Pull request #182: Development
Merge in ITB/xml-validator from development to master * commit '127ceee2865e20fb4a82e5b906728afc3ad8e305': Resolution and caching of imported schemas provided as remote references (user-provided or preconfigured) Use Path instead of File
2 parents 36f93d7 + 127ceee commit 10c200d

10 files changed

Lines changed: 522 additions & 177 deletions

File tree

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/DomainConfig.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,63 @@ public class DomainConfig extends WebDomainConfig {
5454
private Map<String, ContextFileCombinationTemplateConfig> contextFileCombinationTemplateMap;
5555
private Map<String, Path> inputTransformerMap;
5656
private Map<String, Boolean> stopOnXsdErrors;
57+
private Map<String, Path> remoteSchemaImportMappings;
58+
private Map<String, Boolean> preloadRemoteSchemaImports;
59+
private boolean skipRemoteSchemaImportCaching = false;
60+
61+
/**
62+
* @return Whether remote schemas in imports should be cached.
63+
*/
64+
public boolean isSkipRemoteSchemaImportCaching() {
65+
return skipRemoteSchemaImportCaching;
66+
}
67+
68+
/**
69+
* @param skipRemoteSchemaImportCaching Whether remote schemas in imports should be cached.
70+
*/
71+
public void setSkipRemoteSchemaImportCaching(boolean skipRemoteSchemaImportCaching) {
72+
this.skipRemoteSchemaImportCaching = skipRemoteSchemaImportCaching;
73+
}
74+
75+
/**
76+
* @return The map of schema URIs to path locations.
77+
*/
78+
public Map<String, Path> getRemoteSchemaImportMappings() {
79+
return remoteSchemaImportMappings;
80+
}
81+
82+
/**
83+
* @param remoteSchemaImportMappings The map of schema URIs to path locations.
84+
*/
85+
public void setRemoteSchemaImportMappings(Map<String, Path> remoteSchemaImportMappings) {
86+
this.remoteSchemaImportMappings = remoteSchemaImportMappings;
87+
}
88+
89+
/**
90+
* Check to see whether any validation types are set for remote schema import preloading.
91+
*
92+
* @return The check result.
93+
*/
94+
public boolean isPreloadingRemoteSchemaImportsForAnyType() {
95+
return preloadRemoteSchemaImports != null && preloadRemoteSchemaImports.values().stream().anyMatch(preload -> preload);
96+
}
97+
98+
/**
99+
* Check whether remote schema imports should be preloaded at startup for the given validation type.
100+
*
101+
* @param validationType The validation type.
102+
* @return The check result.
103+
*/
104+
public boolean preloadRemoteSchemaImports(String validationType) {
105+
return preloadRemoteSchemaImports != null && preloadRemoteSchemaImports.compute(validationType, (k, flag) -> flag != null && flag);
106+
}
107+
108+
/**
109+
* @param preloadRemoteSchemaImports The map of full validation types to whether remote schema imports should be preloaded at startup.
110+
*/
111+
public void setPreloadRemoteSchemaImports(Map<String, Boolean> preloadRemoteSchemaImports) {
112+
this.preloadRemoteSchemaImports = preloadRemoteSchemaImports;
113+
}
57114

58115
/** @return The map of full validation types to XSLT files for input transformation. */
59116
public Map<String, Path> getInputTransformerMap() {

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/DomainConfigCache.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ protected void addDomainConfiguration(DomainConfig domainConfig, Configuration c
114114
));
115115
// Stop on XSD errors - stop
116116
addMissingDefaultValues(domainConfig.getWebServiceDescription(), appConfig.getDefaultLabels());
117+
// Local mappings for remote schema imports and caching - start
118+
domainConfig.setRemoteSchemaImportMappings(ParseUtils.parseFileMap("validator.remoteSchemaImportMapping", config, "Schema import", appConfig, domainConfig));
119+
domainConfig.setSkipRemoteSchemaImportCaching(config.getBoolean("validator.skipRemoteSchemaImportCaching", false));
120+
if (!domainConfig.isSkipRemoteSchemaImportCaching()) {
121+
domainConfig.setPreloadRemoteSchemaImports(ParseUtils.parseBooleanMap("validator.preloadRemoteSchemaImports", config, domainConfig.getType(), config.getBoolean("validator.preloadRemoteSchemaImports", false)));
122+
}
123+
// Local mappings for remote schema imports and caching - end
117124
}
118125

119126
/**

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/InputHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public List<FileInfo> validateExternalArtifacts(DomainConfig domainConfig, Valid
5353
if (fileInfo.getFile() != null) {
5454
File rootFile = fileManager.unzipFile(parentFolder, fileInfo.getFile());
5555
if (rootFile == null) {
56-
artifactsToReturn.add(new FileInfo(fileManager.preprocessFileIfNeeded(domainConfig, validationType, artifactType, fileInfo.getFile(), true)));
56+
artifactsToReturn.add(new FileInfo(fileManager.preprocessFileIfNeeded(domainConfig, validationType, artifactType, fileInfo.getFile(), true), fileInfo.getType(), fileInfo.getSource()));
5757
} else {
5858
// ZIP File
5959
boolean proceed;

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/ValidationSpecs.java

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,36 @@ public class ValidationSpecs {
8282
private XMLInputFactory xmlInputFactory;
8383
private TransformerFactory transformerFactory;
8484
private Document schematronInputAsDocument;
85+
private boolean validateAgainstSchematrons = true;
86+
private boolean validateAgainstPlugins = true;
87+
private boolean logProgress = true;
8588

8689
/**
8790
* Private constructor to prevent direct initialisation.
8891
*/
8992
private ValidationSpecs() {}
9093

94+
/**
95+
* @return Whether validation progress should be logged.
96+
*/
97+
public boolean isLogProgress() {
98+
return logProgress;
99+
}
100+
101+
/**
102+
* @return Whether the input should be validated against schematrons.
103+
*/
104+
public boolean isValidateAgainstSchematrons() {
105+
return validateAgainstSchematrons;
106+
}
107+
108+
/**
109+
* @return Whether the input should be validated against custom plugins.
110+
*/
111+
public boolean isValidateAgainstPlugins() {
112+
return validateAgainstPlugins;
113+
}
114+
91115
/**
92116
* @return The pretty-printed JSON content to validate.
93117
*/
@@ -357,8 +381,15 @@ public File getInputFileToUse() throws XMLInvalidException {
357381
* @return The list of XSDs.
358382
*/
359383
public List<FileInfo> getSchemasToUse(FileManager fileManager) {
360-
List<FileInfo> schemaFiles = fileManager.getPreconfiguredValidationArtifacts(getDomainConfig(), getValidationType(), DomainConfig.ARTIFACT_TYPE_SCHEMA);
361-
schemaFiles.addAll(getExternalSchemas());
384+
List<FileInfo> schemaFiles = new ArrayList<>();
385+
List<FileInfo> preconfiguredSchemas = fileManager.getPreconfiguredValidationArtifacts(getDomainConfig(), getValidationType(), DomainConfig.ARTIFACT_TYPE_SCHEMA);
386+
if (preconfiguredSchemas != null) {
387+
schemaFiles.addAll(preconfiguredSchemas);
388+
}
389+
List<FileInfo> externalSchemas = getExternalSchemas();
390+
if (externalSchemas != null) {
391+
schemaFiles.addAll(externalSchemas);
392+
}
362393
return schemaFiles;
363394
}
364395

@@ -539,7 +570,7 @@ private void validateContextFiles() throws ValidatorException {
539570
inputStream,
540571
schemaStream,
541572
errorHandler,
542-
applicationContext.getBean(XSDFileResolver.class, getValidationType(), getDomainConfig(), schemaFile.getParent()),
573+
applicationContext.getBean(XSDFileResolver.class, getDomainConfig(), schemaFile.toURI()),
543574
getLocalisationHelper().getLocale()
544575
);
545576
} catch (Exception e) {
@@ -751,6 +782,36 @@ public Builder withTempFolder(Path tempFolder) {
751782
return this;
752783
}
753784

785+
/**
786+
* Skip schematron validation.
787+
*
788+
* @return The builder.
789+
*/
790+
public Builder skipSchematronValidation() {
791+
instance.validateAgainstSchematrons = false;
792+
return this;
793+
}
794+
795+
/**
796+
* Skip plugin validation.
797+
*
798+
* @return The builder.
799+
*/
800+
public Builder skipPluginValidation() {
801+
instance.validateAgainstPlugins = false;
802+
return this;
803+
}
804+
805+
/**
806+
* Skip progress logging.
807+
*
808+
* @return The builder instance.
809+
*/
810+
public Builder skipProgressLogging() {
811+
this.instance.logProgress = false;
812+
return this;
813+
}
814+
754815
}
755816

756817
/**
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (C) 2025 European Union
3+
*
4+
* Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European Commission - subsequent
5+
* versions of the EUPL (the "Licence"); You may not use this work except in compliance with the Licence.
6+
*
7+
* You may obtain a copy of the Licence at:
8+
*
9+
* https://interoperable-europe.ec.europa.eu/collection/eupl/eupl-text-eupl-12
10+
*
11+
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is distributed on an
12+
* "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Licence for
13+
* the specific language governing permissions and limitations under the Licence.
14+
*/
15+
16+
package eu.europa.ec.itb.xml.config;
17+
18+
import eu.europa.ec.itb.validation.commons.LocalisationHelper;
19+
import eu.europa.ec.itb.validation.commons.Utils;
20+
import eu.europa.ec.itb.validation.plugin.PluginManager;
21+
import eu.europa.ec.itb.xml.DomainConfig;
22+
import eu.europa.ec.itb.xml.DomainConfigCache;
23+
import eu.europa.ec.itb.xml.ValidationSpecs;
24+
import eu.europa.ec.itb.xml.util.FileManager;
25+
import eu.europa.ec.itb.xml.validation.XMLValidator;
26+
import jakarta.annotation.PostConstruct;
27+
import org.apache.commons.io.FileUtils;
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.context.ApplicationContext;
32+
import org.springframework.context.annotation.Configuration;
33+
34+
import java.io.File;
35+
import java.nio.file.Files;
36+
37+
/**
38+
* Configuration class to trigger the preloading of XSD imports for the domains where this is enabled.
39+
*/
40+
@Configuration
41+
public class ResourcePreloader {
42+
43+
private static final Logger LOG = LoggerFactory.getLogger(ResourcePreloader.class);
44+
45+
@Autowired
46+
private DomainConfigCache domainConfigs = null;
47+
@Autowired
48+
private ApplicationContext ctx = null;
49+
@Autowired
50+
FileManager fileManager;
51+
@Autowired
52+
private PluginManager pluginManager = null;
53+
54+
@PostConstruct
55+
public void initialise() {
56+
// Initialise plugins.
57+
if (pluginManager.hasPlugins()) {
58+
LOG.info("Initialised plugins");
59+
}
60+
// Preload XSD imports.
61+
domainConfigs.getAllDomainConfigurations().stream().filter(DomainConfig::isPreloadingRemoteSchemaImportsForAnyType).forEach(domainConfig -> {
62+
LOG.info("Preloading remote schema imports for domain [{}]", domainConfig.getDomainName());
63+
var localiser = new LocalisationHelper(domainConfig, Utils.getSupportedLocale(null, domainConfig));
64+
// Iterate over validation types.
65+
domainConfig.getType().stream().filter(domainConfig::preloadRemoteSchemaImports).forEach(validationType -> {
66+
// Trigger the preloading and caching of import references by making a XSD-only validation of dummy content.
67+
LOG.info("Preloading remote schema imports for validation type [{}]", validationType);
68+
File tempFolderForRequest = fileManager.createTemporaryFolderPath();
69+
try {
70+
// Prepare a dummy empty XML file to trigger the validation.
71+
File inputFile = fileManager.getFileFromString(tempFolderForRequest, "<empty/>");
72+
ValidationSpecs specs = ValidationSpecs.builder(inputFile, localiser, domainConfig, ctx)
73+
.addInputToReport(false)
74+
.locationAsPath(true)
75+
.withTempFolder(tempFolderForRequest.toPath())
76+
.withValidationType(validationType)
77+
.skipSchematronValidation()
78+
.skipPluginValidation()
79+
.skipProgressLogging()
80+
.build();
81+
XMLValidator validator = ctx.getBean(XMLValidator.class, specs);
82+
validator.validateAll();
83+
} catch (Exception e) {
84+
LOG.warn("Failed to preload remote schema imports for domain [{}]", domainConfig.getDomainName(), e);
85+
} finally {
86+
// Cleanup temporary resources for request.
87+
if (Files.exists(tempFolderForRequest.toPath())) {
88+
FileUtils.deleteQuietly(tempFolderForRequest);
89+
}
90+
}
91+
});
92+
});
93+
}
94+
95+
}

0 commit comments

Comments
 (0)