CvMappingValidator.java
/*
* Copyright 2018 Leibniz-Institut für Analytische Wissenschaften – ISAS – e.V..
*
* 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.
*/
package de.isas.mztab2.validation;
import de.isas.lipidomics.mztab2.validation.Validator;
import de.isas.mztab2.cvmapping.CvMappingUtils;
import de.isas.mztab2.cvmapping.CvParameterLookupService;
import de.isas.mztab2.cvmapping.JxPathElement;
import de.isas.mztab2.cvmapping.RemoveUserParams;
import de.isas.mztab2.cvmapping.RuleEvaluationResult;
import de.isas.mztab2.model.MzTab;
import de.isas.mztab2.model.Parameter;
import de.isas.mztab2.model.ValidationMessage;
import de.isas.mztab2.validation.handlers.AndValidationHandler;
import de.isas.mztab2.validation.handlers.EmptyRuleHandler;
import de.isas.mztab2.validation.handlers.ExtraParametersValidationHandler;
import de.isas.mztab2.validation.handlers.OrValidationHandler;
import de.isas.mztab2.validation.handlers.ResolvingCvRuleHandler;
import de.isas.mztab2.validation.handlers.SharedParametersValidationHandler;
import de.isas.mztab2.validation.handlers.XorValidationHandler;
import info.psidev.cvmapping.CvMapping;
import info.psidev.cvmapping.CvMappingRule;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.Pointer;
import org.apache.commons.lang3.tuple.Pair;
import uk.ac.ebi.pride.jmztab2.utils.errors.CrossCheckErrorType;
import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabError;
import uk.ac.ebi.pride.utilities.ols.web.service.client.OLSClient;
import uk.ac.ebi.pride.utilities.ols.web.service.config.OLSWsConfig;
/**
* Validator implementation that uses a provided xml mapping file with rules for
* required, recommended and optional CV parameters to assert that an mzTab
* follows these rules.
*
* First, all preValidators are run, then, the cv parameter validation is executed, before finally,
* the postValidators are run. Each validator can add validation messages to the output.
*
* @author nilshoffmann
*/
@Slf4j
@lombok.Builder()
public class CvMappingValidator implements Validator<MzTab> {
private final CvMapping mapping;
private final CvRuleHandler ruleHandler;
private final boolean errorIfTermNotInRule;
private final CvTermValidationHandler andHandler;
private final CvTermValidationHandler orHandler;
private final CvTermValidationHandler xorHandler;
private final CvTermValidationHandler extraHandler;
private final CvTermValidationHandler sharedHandler;
private final EmptyRuleHandler emptyRuleHandler;
private final RemoveUserParams cvTermSelectionHandler;
private final List<Validator<MzTab>> preValidators = new LinkedList<>();
private final List<Validator<MzTab>> postValidators = new LinkedList<>();
/**
* Create a new instance of CvMappingValidator.
*
* Uses a default instance of the {@link CvParameterLookupService}.
*
* @param mappingFile the mapping file to use
* @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element
* @return a new CvMappingValidator instance
* @throws JAXBException if errors occur during unmarshalling of the mapping xml file.
*/
public static CvMappingValidator of(File mappingFile,
boolean errorIfTermNotInRule) throws JAXBException {
OLSWsConfig config = new OLSWsConfig();
OLSClient client = new OLSClient(config);
CvParameterLookupService service = new CvParameterLookupService(client);
return of(mappingFile, service, errorIfTermNotInRule);
}
/**
* Create a new instance of CvMappingValidator.
*
* Uses the provided {@link CvParameterLookupService}.
*
* @param mappingFile the mapping file to use
* @param client the ontology lookup service client
* @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element
* @return a new CvMappingValidator instance
* @throws JAXBException if errors occur during unmarshalling of the mapping xml file.
*/
public static CvMappingValidator of(File mappingFile,
CvParameterLookupService client, boolean errorIfTermNotInRule) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(CvMapping.class);
Unmarshaller u = jaxbContext.createUnmarshaller();
CvMapping mapping = (CvMapping) u.unmarshal(mappingFile);
return new CvMappingValidator.CvMappingValidatorBuilder().mapping(
mapping).
ruleHandler(new ResolvingCvRuleHandler(client)).
errorIfTermNotInRule(errorIfTermNotInRule).
andHandler(new AndValidationHandler()).
orHandler(new OrValidationHandler()).
xorHandler(new XorValidationHandler()).
extraHandler(new ExtraParametersValidationHandler()).
sharedHandler(new SharedParametersValidationHandler()).
cvTermSelectionHandler(new RemoveUserParams()).
emptyRuleHandler(new EmptyRuleHandler()).
build().
withPreValidator(new CvDefinitionValidationHandler());
}
/**
* Add the provided validator implementation to the list of validators that run <b>first</b>.
* @param preValidator the validator
* @return an instance of this object
*/
public CvMappingValidator withPreValidator(Validator<MzTab> preValidator) {
preValidators.add(preValidator);
return this;
}
/**
* Add the provided validator implementation to the list of validators that run <b>last</b>.
* @param postValidator the validator
* @return an instance of this object
*/
public CvMappingValidator withPostValidator(Validator<MzTab> postValidator) {
postValidators.add(postValidator);
return this;
}
/**
* Create a new instance of CvMappingValidator.
*
* Uses a default instance of the {@link CvParameterLookupService}.
*
* @param mappingFile the mapping file URL to use
* @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element
* @return a new CvMappingValidator instance
* @throws JAXBException if errors occur during unmarshalling of the mapping xml file.
*/
public static CvMappingValidator of(URL mappingFile,
boolean errorIfTermNotInRule) throws JAXBException {
OLSWsConfig config = new OLSWsConfig();
OLSClient client = new OLSClient(config);
CvParameterLookupService service = new CvParameterLookupService(client);
return of(mappingFile, service, errorIfTermNotInRule);
}
/**
* Create a new instance of CvMappingValidator.
*
* Uses the provided {@link CvParameterLookupService}.
*
* @param mappingFile the mapping file URL to use
* @param client the ontology lookup service client
* @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element
* @return a new CvMappingValidator instance
* @throws JAXBException if errors occur during unmarshalling of the mapping xml file.
*/
public static CvMappingValidator of(URL mappingFile,
CvParameterLookupService client, boolean errorIfTermNotInRule) throws JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(CvMapping.class);
Unmarshaller u = jaxbContext.createUnmarshaller();
CvMapping mapping = (CvMapping) u.unmarshal(mappingFile);
return new CvMappingValidator.CvMappingValidatorBuilder().mapping(
mapping).
ruleHandler(new ResolvingCvRuleHandler(client)).
errorIfTermNotInRule(errorIfTermNotInRule).
andHandler(new AndValidationHandler()).
orHandler(new OrValidationHandler()).
xorHandler(new XorValidationHandler()).
extraHandler(new ExtraParametersValidationHandler()).
sharedHandler(new SharedParametersValidationHandler()).
cvTermSelectionHandler(new RemoveUserParams()).
emptyRuleHandler(new EmptyRuleHandler()).
build().
withPreValidator(new CvDefinitionValidationHandler());
}
/**
* Create a new instance of CvMappingValidator.
*
* Uses the provided {@link CvParameterLookupService}.
*
* @param mapping the cv mapping to use
* @param client the ontology lookup service client
* @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element
* @return a new CvMappingValidator instance
*/
public static CvMappingValidator of(CvMapping mapping,
CvParameterLookupService client,
boolean errorIfTermNotInRule) {
return new CvMappingValidator.CvMappingValidatorBuilder().mapping(
mapping).
ruleHandler(new ResolvingCvRuleHandler(client)).
errorIfTermNotInRule(errorIfTermNotInRule).
andHandler(new AndValidationHandler()).
orHandler(new OrValidationHandler()).
xorHandler(new XorValidationHandler()).
extraHandler(new ExtraParametersValidationHandler()).
sharedHandler(new SharedParametersValidationHandler()).
cvTermSelectionHandler(new RemoveUserParams()).
emptyRuleHandler(new EmptyRuleHandler()).
build().
withPreValidator(new CvDefinitionValidationHandler());
}
@Override
public List<ValidationMessage> validate(MzTab mzTab) {
final List<ValidationMessage> messages = new LinkedList<>();
log.debug("Applying {} pre validation steps.", preValidators.size());
preValidators.stream().
forEach((validator) ->
{
messages.addAll(validator.validate(mzTab));
});
messages.addAll(new CvDefinitionValidationHandler().validate(mzTab));
JXPathContext context = JXPathContext.newContext(mzTab);
log.debug("Applying {} cv rule mapping steps.", mapping.
getCvMappingRuleList().
getCvMappingRule().
size());
mapping.getCvMappingRuleList().
getCvMappingRule().
forEach((rule) ->
{
messages.addAll(handleRule(context, rule, errorIfTermNotInRule));
});
log.debug("Applying {} post validation steps.", preValidators.size());
postValidators.stream().
forEach((validator) ->
{
messages.addAll(validator.validate(mzTab));
});
return messages;
}
private List<ValidationMessage> handleRule(JXPathContext context,
CvMappingRule rule, boolean errorOnTermNotInRule) {
String path = rule.getCvElementPath();
List<Pair<Pointer, Parameter>> selection = JxPathElement.
toList(context, path, Parameter.class);
final List<ValidationMessage> messages = emptyRuleHandler.handleRule(
rule, selection);
if (!messages.isEmpty()) {
return messages;
}
final List<Pair<Pointer, Parameter>> filteredSelection = cvTermSelectionHandler.
handleSelection(selection);
// and logic means that ALL of the defined terms or their children MUST appear
// we only compare valid CVParameters here, user Params (no cv accession), are not compared!
// if combination logic is AND, child expansion needs to be disabled to avoid nonsensical combinations
try {
RuleEvaluationResult result = ruleHandler.handleRule(rule,
filteredSelection);
switch (rule.getCvTermsCombinationLogic()) {
case AND:
messages.addAll(andHandler.handleParameters(result,
errorOnTermNotInRule));
break;
case OR: // any of the terms or their children need to appear
messages.addAll(orHandler.handleParameters(result,
errorOnTermNotInRule));
break;
case XOR:
messages.addAll(xorHandler.handleParameters(result,
errorOnTermNotInRule));
break;
default:
throw new IllegalArgumentException(
"Unknown combination logic value: " + rule.
getCvTermsCombinationLogic() + " on rule " + CvMappingUtils.
niceToString(rule) + "! Supported are: " + Arrays.
toString(CvMappingRule.CvTermsCombinationLogic.
values()));
}
// messages.addAll(sharedHandler.handleParameters(result, errorOnTermNotInRule));
messages.addAll(extraHandler.handleParameters(result,
errorOnTermNotInRule));
} catch (RuntimeException re) {
log.error(
"Caught exception while running semantic validation on rule " + CvMappingUtils.
niceToString(rule) + " with selection " + filteredSelection,
re);
MZTabError error = new MZTabError(
CrossCheckErrorType.SemanticValidationException, -1, re.
getMessage());
messages.add(error.toValidationMessage());
}
return messages.isEmpty() ? Collections.emptyList() : messages;
}
}