001/* 002 * Copyright 2018 Leibniz-Institut für Analytische Wissenschaften – ISAS – e.V.. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.isas.mztab2.validation; 017 018import de.isas.lipidomics.mztab2.validation.Validator; 019import de.isas.mztab2.cvmapping.CvMappingUtils; 020import de.isas.mztab2.cvmapping.CvParameterLookupService; 021import de.isas.mztab2.cvmapping.JxPathElement; 022import de.isas.mztab2.cvmapping.RemoveUserParams; 023import de.isas.mztab2.cvmapping.RuleEvaluationResult; 024import de.isas.mztab2.model.MzTab; 025import de.isas.mztab2.model.Parameter; 026import de.isas.mztab2.model.ValidationMessage; 027import de.isas.mztab2.validation.handlers.AndValidationHandler; 028import de.isas.mztab2.validation.handlers.EmptyRuleHandler; 029import de.isas.mztab2.validation.handlers.ExtraParametersValidationHandler; 030import de.isas.mztab2.validation.handlers.OrValidationHandler; 031import de.isas.mztab2.validation.handlers.ResolvingCvRuleHandler; 032import de.isas.mztab2.validation.handlers.SharedParametersValidationHandler; 033import de.isas.mztab2.validation.handlers.XorValidationHandler; 034import info.psidev.cvmapping.CvMapping; 035import info.psidev.cvmapping.CvMappingRule; 036import java.io.File; 037import java.net.URL; 038import java.util.Arrays; 039import java.util.Collections; 040import java.util.LinkedList; 041import java.util.List; 042import javax.xml.bind.JAXBContext; 043import javax.xml.bind.JAXBException; 044import javax.xml.bind.Unmarshaller; 045import lombok.extern.slf4j.Slf4j; 046import org.apache.commons.jxpath.JXPathContext; 047import org.apache.commons.jxpath.Pointer; 048import org.apache.commons.lang3.tuple.Pair; 049import uk.ac.ebi.pride.jmztab2.utils.errors.CrossCheckErrorType; 050import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabError; 051import uk.ac.ebi.pride.utilities.ols.web.service.client.OLSClient; 052import uk.ac.ebi.pride.utilities.ols.web.service.config.OLSWsConfig; 053 054/** 055 * Validator implementation that uses a provided xml mapping file with rules for 056 * required, recommended and optional CV parameters to assert that an mzTab 057 * follows these rules. 058 * 059 * First, all preValidators are run, then, the cv parameter validation is executed, before finally, 060 * the postValidators are run. Each validator can add validation messages to the output. 061 * 062 * @author nilshoffmann 063 */ 064@Slf4j 065@lombok.Builder() 066public class CvMappingValidator implements Validator<MzTab> { 067 068 private final CvMapping mapping; 069 private final CvRuleHandler ruleHandler; 070 private final boolean errorIfTermNotInRule; 071 private final CvTermValidationHandler andHandler; 072 private final CvTermValidationHandler orHandler; 073 private final CvTermValidationHandler xorHandler; 074 private final CvTermValidationHandler extraHandler; 075 private final CvTermValidationHandler sharedHandler; 076 private final EmptyRuleHandler emptyRuleHandler; 077 private final RemoveUserParams cvTermSelectionHandler; 078 private final List<Validator<MzTab>> preValidators = new LinkedList<>(); 079 private final List<Validator<MzTab>> postValidators = new LinkedList<>(); 080 081 /** 082 * Create a new instance of CvMappingValidator. 083 * 084 * Uses a default instance of the {@link CvParameterLookupService}. 085 * 086 * @param mappingFile the mapping file to use 087 * @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element 088 * @return a new CvMappingValidator instance 089 * @throws JAXBException if errors occur during unmarshalling of the mapping xml file. 090 */ 091 public static CvMappingValidator of(File mappingFile, 092 boolean errorIfTermNotInRule) throws JAXBException { 093 OLSWsConfig config = new OLSWsConfig(); 094 OLSClient client = new OLSClient(config); 095 CvParameterLookupService service = new CvParameterLookupService(client); 096 return of(mappingFile, service, errorIfTermNotInRule); 097 } 098 099 /** 100 * Create a new instance of CvMappingValidator. 101 * 102 * Uses the provided {@link CvParameterLookupService}. 103 * 104 * @param mappingFile the mapping file to use 105 * @param client the ontology lookup service client 106 * @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element 107 * @return a new CvMappingValidator instance 108 * @throws JAXBException if errors occur during unmarshalling of the mapping xml file. 109 */ 110 public static CvMappingValidator of(File mappingFile, 111 CvParameterLookupService client, boolean errorIfTermNotInRule) throws JAXBException { 112 113 JAXBContext jaxbContext = JAXBContext.newInstance(CvMapping.class); 114 Unmarshaller u = jaxbContext.createUnmarshaller(); 115 CvMapping mapping = (CvMapping) u.unmarshal(mappingFile); 116 return new CvMappingValidator.CvMappingValidatorBuilder().mapping( 117 mapping). 118 ruleHandler(new ResolvingCvRuleHandler(client)). 119 errorIfTermNotInRule(errorIfTermNotInRule). 120 andHandler(new AndValidationHandler()). 121 orHandler(new OrValidationHandler()). 122 xorHandler(new XorValidationHandler()). 123 extraHandler(new ExtraParametersValidationHandler()). 124 sharedHandler(new SharedParametersValidationHandler()). 125 cvTermSelectionHandler(new RemoveUserParams()). 126 emptyRuleHandler(new EmptyRuleHandler()). 127 build(). 128 withPreValidator(new CvDefinitionValidationHandler()); 129 } 130 131 /** 132 * Add the provided validator implementation to the list of validators that run <b>first</b>. 133 * @param preValidator the validator 134 * @return an instance of this object 135 */ 136 public CvMappingValidator withPreValidator(Validator<MzTab> preValidator) { 137 preValidators.add(preValidator); 138 return this; 139 } 140 141 /** 142 * Add the provided validator implementation to the list of validators that run <b>last</b>. 143 * @param postValidator the validator 144 * @return an instance of this object 145 */ 146 public CvMappingValidator withPostValidator(Validator<MzTab> postValidator) { 147 postValidators.add(postValidator); 148 return this; 149 } 150 151 /** 152 * Create a new instance of CvMappingValidator. 153 * 154 * Uses a default instance of the {@link CvParameterLookupService}. 155 * 156 * @param mappingFile the mapping file URL to use 157 * @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element 158 * @return a new CvMappingValidator instance 159 * @throws JAXBException if errors occur during unmarshalling of the mapping xml file. 160 */ 161 public static CvMappingValidator of(URL mappingFile, 162 boolean errorIfTermNotInRule) throws JAXBException { 163 OLSWsConfig config = new OLSWsConfig(); 164 OLSClient client = new OLSClient(config); 165 CvParameterLookupService service = new CvParameterLookupService(client); 166 return of(mappingFile, service, errorIfTermNotInRule); 167 } 168 169 /** 170 * Create a new instance of CvMappingValidator. 171 * 172 * Uses the provided {@link CvParameterLookupService}. 173 * 174 * @param mappingFile the mapping file URL to use 175 * @param client the ontology lookup service client 176 * @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element 177 * @return a new CvMappingValidator instance 178 * @throws JAXBException if errors occur during unmarshalling of the mapping xml file. 179 */ 180 public static CvMappingValidator of(URL mappingFile, 181 CvParameterLookupService client, boolean errorIfTermNotInRule) throws JAXBException { 182 JAXBContext jaxbContext = JAXBContext.newInstance(CvMapping.class); 183 Unmarshaller u = jaxbContext.createUnmarshaller(); 184 CvMapping mapping = (CvMapping) u.unmarshal(mappingFile); 185 return new CvMappingValidator.CvMappingValidatorBuilder().mapping( 186 mapping). 187 ruleHandler(new ResolvingCvRuleHandler(client)). 188 errorIfTermNotInRule(errorIfTermNotInRule). 189 andHandler(new AndValidationHandler()). 190 orHandler(new OrValidationHandler()). 191 xorHandler(new XorValidationHandler()). 192 extraHandler(new ExtraParametersValidationHandler()). 193 sharedHandler(new SharedParametersValidationHandler()). 194 cvTermSelectionHandler(new RemoveUserParams()). 195 emptyRuleHandler(new EmptyRuleHandler()). 196 build(). 197 withPreValidator(new CvDefinitionValidationHandler()); 198 } 199 200 /** 201 * Create a new instance of CvMappingValidator. 202 * 203 * Uses the provided {@link CvParameterLookupService}. 204 * 205 * @param mapping the cv mapping to use 206 * @param client the ontology lookup service client 207 * @param errorIfTermNotInRule raise an error if a term is not defined within an otherwise matching rule for the element 208 * @return a new CvMappingValidator instance 209 */ 210 public static CvMappingValidator of(CvMapping mapping, 211 CvParameterLookupService client, 212 boolean errorIfTermNotInRule) { 213 return new CvMappingValidator.CvMappingValidatorBuilder().mapping( 214 mapping). 215 ruleHandler(new ResolvingCvRuleHandler(client)). 216 errorIfTermNotInRule(errorIfTermNotInRule). 217 andHandler(new AndValidationHandler()). 218 orHandler(new OrValidationHandler()). 219 xorHandler(new XorValidationHandler()). 220 extraHandler(new ExtraParametersValidationHandler()). 221 sharedHandler(new SharedParametersValidationHandler()). 222 cvTermSelectionHandler(new RemoveUserParams()). 223 emptyRuleHandler(new EmptyRuleHandler()). 224 build(). 225 withPreValidator(new CvDefinitionValidationHandler()); 226 } 227 228 @Override 229 public List<ValidationMessage> validate(MzTab mzTab) { 230 final List<ValidationMessage> messages = new LinkedList<>(); 231 log.debug("Applying {} pre validation steps.", preValidators.size()); 232 preValidators.stream(). 233 forEach((validator) -> 234 { 235 messages.addAll(validator.validate(mzTab)); 236 }); 237 messages.addAll(new CvDefinitionValidationHandler().validate(mzTab)); 238 JXPathContext context = JXPathContext.newContext(mzTab); 239 log.debug("Applying {} cv rule mapping steps.", mapping. 240 getCvMappingRuleList(). 241 getCvMappingRule(). 242 size()); 243 mapping.getCvMappingRuleList(). 244 getCvMappingRule(). 245 forEach((rule) -> 246 { 247 messages.addAll(handleRule(context, rule, errorIfTermNotInRule)); 248 }); 249 log.debug("Applying {} post validation steps.", preValidators.size()); 250 postValidators.stream(). 251 forEach((validator) -> 252 { 253 messages.addAll(validator.validate(mzTab)); 254 }); 255 return messages; 256 } 257 258 private List<ValidationMessage> handleRule(JXPathContext context, 259 CvMappingRule rule, boolean errorOnTermNotInRule) { 260 String path = rule.getCvElementPath(); 261 List<Pair<Pointer, Parameter>> selection = JxPathElement. 262 toList(context, path, Parameter.class); 263 264 final List<ValidationMessage> messages = emptyRuleHandler.handleRule( 265 rule, selection); 266 if (!messages.isEmpty()) { 267 return messages; 268 } 269 270 final List<Pair<Pointer, Parameter>> filteredSelection = cvTermSelectionHandler. 271 handleSelection(selection); 272 273 // and logic means that ALL of the defined terms or their children MUST appear 274 // we only compare valid CVParameters here, user Params (no cv accession), are not compared! 275 // if combination logic is AND, child expansion needs to be disabled to avoid nonsensical combinations 276 try { 277 RuleEvaluationResult result = ruleHandler.handleRule(rule, 278 filteredSelection); 279 280 switch (rule.getCvTermsCombinationLogic()) { 281 case AND: 282 messages.addAll(andHandler.handleParameters(result, 283 errorOnTermNotInRule)); 284 break; 285 case OR: // any of the terms or their children need to appear 286 messages.addAll(orHandler.handleParameters(result, 287 errorOnTermNotInRule)); 288 break; 289 case XOR: 290 messages.addAll(xorHandler.handleParameters(result, 291 errorOnTermNotInRule)); 292 break; 293 default: 294 throw new IllegalArgumentException( 295 "Unknown combination logic value: " + rule. 296 getCvTermsCombinationLogic() + " on rule " + CvMappingUtils. 297 niceToString(rule) + "! Supported are: " + Arrays. 298 toString(CvMappingRule.CvTermsCombinationLogic. 299 values())); 300 } 301 // messages.addAll(sharedHandler.handleParameters(result, errorOnTermNotInRule)); 302 messages.addAll(extraHandler.handleParameters(result, 303 errorOnTermNotInRule)); 304 } catch (RuntimeException re) { 305 log.error( 306 "Caught exception while running semantic validation on rule " + CvMappingUtils. 307 niceToString(rule) + " with selection " + filteredSelection, 308 re); 309 MZTabError error = new MZTabError( 310 CrossCheckErrorType.SemanticValidationException, -1, re. 311 getMessage()); 312 messages.add(error.toValidationMessage()); 313 } 314 return messages.isEmpty() ? Collections.emptyList() : messages; 315 } 316 317}