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.io; 017 018import de.isas.lipidomics.mztab2.validation.MzTabValidator; 019import de.isas.lipidomics.mztab2.validation.Validator; 020import de.isas.mztab2.model.MzTab; 021import de.isas.mztab2.model.ValidationMessage; 022import java.io.File; 023import java.io.FileOutputStream; 024import java.io.IOException; 025import java.io.OutputStream; 026import java.io.OutputStreamWriter; 027import java.nio.charset.StandardCharsets; 028import java.nio.file.Path; 029import java.util.Collections; 030import java.util.List; 031import java.util.Optional; 032import java.util.UUID; 033import java.util.stream.Collectors; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036import uk.ac.ebi.pride.jmztab2.utils.errors.MZTabErrorType.Level; 037 038/** 039 * <p> 040 * MzTabValidatingWriter allows to write MzTab objects after validation with a 041 * custom or default validator. Use this if you want to make sure that your fail 042 * satisfies the structural and minimal reporting constraints of mzTab. 043 * Otherwise, use the MzTabNonValidatingWriter.</p> 044 * 045 * <p> 046 * To create a <b>validating</b> writer using the default checks also applied by 047 * the parser, call:</p> 048 * {@code MzTabWriter validatingWriter = new MzTabValidatingWriter.Default();} 049 * <p> 050 * Otherwise, to create a non-validating instance, call:</p> 051 * {@code MzTabWriter plainWriter = new MzTabNonValidatingWriter();} 052 * 053 * @author nilshoffmann 054 * @see MzTabValidatingWriter 055 * @see MzTabValidator 056 */ 057public class MzTabValidatingWriter implements MzTabWriter<List<ValidationMessage>> { 058 059 private static final Logger LOGGER = LoggerFactory.getLogger( 060 MzTabValidatingWriter.class); 061 062 private final Validator<MzTab> validator; 063 private final boolean skipWriteOnValidationFailure; 064 private final MzTabWriterDefaults writerDefaults; 065 private List<ValidationMessage> validationMessages = null; 066 067 /** 068 * Uses default structural validation based on writing and parsing the 069 * written file with the default parsing checks. The output file will not be 070 * written, if any validation failures occur. 071 */ 072 public MzTabValidatingWriter() { 073 this(new WriteAndParseValidator(System.out, Level.Info, 100), 074 new MzTabWriterDefaults(), true); 075 } 076 077 /** 078 * Uses the provided validator and default writer configuration. The output 079 * file will not be written, if any validation failures occur. 080 * 081 * @param validator the validator instance. 082 * @param skipWriteOnValidationFailure if true, skips writing of the file if 083 * validation fails. 084 */ 085 public MzTabValidatingWriter(Validator<MzTab> validator, 086 boolean skipWriteOnValidationFailure) { 087 this(validator, new MzTabWriterDefaults(), skipWriteOnValidationFailure); 088 } 089 090 /** 091 * Uses the provided validator and writerDefaults. 092 * 093 * @param validator the validator instance. 094 * @param writerDefaults the default writer settings. 095 * @param skipWriteOnValidationFailure if true, skips writing of the file if 096 * validation fails. 097 */ 098 public MzTabValidatingWriter(Validator<MzTab> validator, 099 MzTabWriterDefaults writerDefaults, boolean skipWriteOnValidationFailure) { 100 this.validator = validator; 101 this.writerDefaults = writerDefaults; 102 this.skipWriteOnValidationFailure = skipWriteOnValidationFailure; 103 } 104 105 /** 106 * A default validator implemenation that first writes and then parses the 107 * created temporary file, performing the parser checks. 108 */ 109 public static class WriteAndParseValidator implements Validator<MzTab> { 110 111 private static final Logger LOGGER = LoggerFactory.getLogger( 112 WriteAndParseValidator.class); 113 114 private final OutputStream outputStream; 115 private final Level level; 116 private final int maxErrorCount; 117 118 /** 119 * Create a new instance of this validator. 120 * 121 * @param outputStream the output stream to write to. 122 * @param level the error level for validation. 123 * @param maxErrorCount the maximum number of errors before an overflow 124 * exception while stop further processing. 125 */ 126 public WriteAndParseValidator(OutputStream outputStream, Level level, 127 int maxErrorCount) { 128 this.outputStream = outputStream; 129 this.level = level; 130 this.maxErrorCount = maxErrorCount; 131 } 132 133 @Override 134 public List<ValidationMessage> validate(MzTab mzTab) { 135 MzTabNonValidatingWriter writer = new MzTabNonValidatingWriter(); 136 File mzTabFile = null; 137 try { 138 mzTabFile = File.createTempFile(UUID.randomUUID(). 139 toString(), ".mztab"); 140 try (OutputStreamWriter osw = new OutputStreamWriter( 141 new FileOutputStream(mzTabFile), 142 StandardCharsets.UTF_8)) { 143 writer. 144 write( 145 osw, mzTab); 146 147 MzTabFileParser parser = new MzTabFileParser(mzTabFile); 148 parser.parse(outputStream, level, maxErrorCount); 149 return parser.getErrorList(). 150 convertToValidationMessages(); 151 } 152 } catch (IOException ex) { 153 LOGGER.error( 154 "Caught exception while trying to parse " + mzTabFile, ex); 155 } finally { 156 if (mzTabFile != null && mzTabFile.exists()) { 157 if(!mzTabFile.delete()) { 158 LOGGER.warn("Deletion of "+mzTabFile+" failed!"); 159 } 160 } 161 } 162 return Collections.emptyList(); 163 } 164 } 165 166 @Override 167 public Optional<List<ValidationMessage>> write(OutputStreamWriter writer, 168 MzTab mzTab) throws IOException { 169 this.validationMessages = Optional.ofNullable(validator.validate(mzTab)). 170 orElse(Collections.emptyList()); 171 if (skipWriteOnValidationFailure && !this.validationMessages.isEmpty()) { 172 return Optional.of(this.validationMessages); 173 } 174 new MzTabNonValidatingWriter(writerDefaults).write(writer, mzTab); 175 return Optional.of(this.validationMessages); 176 } 177 178 @Override 179 public Optional<List<ValidationMessage>> write(Path path, MzTab mzTab) throws IOException { 180 this.validationMessages = Optional.ofNullable(validator.validate(mzTab)). 181 orElse(Collections.emptyList()); 182 if (skipWriteOnValidationFailure && !this.validationMessages.isEmpty()) { 183 return Optional.of(this.validationMessages); 184 } 185 new MzTabNonValidatingWriter(writerDefaults).write(path, mzTab); 186 return Optional.of(this.validationMessages); 187 } 188 189 /** 190 * Returns all validation messages ONLY at the given level. E.g. if you 191 * provide Info, you will ONLY receive Info messages, even if Warn or Error 192 * messages have been produced! 193 * 194 * @param validationMessages the messages to apply the filter on. 195 * @param level the message level. 196 * @return the list of validation messages matching the provided level. 197 */ 198 public static List<ValidationMessage> getValidationMessagesForLevel( 199 Optional<List<ValidationMessage>> validationMessages, 200 ValidationMessage.MessageTypeEnum level) { 201 return validationMessages.orElse(Collections.emptyList()). 202 stream(). 203 filter((message) -> 204 { 205 return message.getMessageType() == level; 206 }). 207 collect(Collectors.toList()); 208 } 209 210}