
001package ca.uhn.fhir.validation; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.rest.api.Constants; 025import ca.uhn.fhir.util.OperationOutcomeUtil; 026import org.hl7.fhir.instance.model.api.IBase; 027import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 028 029import java.util.Collections; 030import java.util.List; 031 032import static org.apache.commons.lang3.StringUtils.isNotBlank; 033 034/** 035 * Encapsulates the results of validation 036 * 037 * @see ca.uhn.fhir.validation.FhirValidator 038 * @since 0.7 039 */ 040public class ValidationResult { 041 public static final int ERROR_DISPLAY_LIMIT_DEFAULT = 1; 042 043 private final FhirContext myCtx; 044 private final boolean myIsSuccessful; 045 private final List<SingleValidationMessage> myMessages; 046 047 private int myErrorDisplayLimit = ERROR_DISPLAY_LIMIT_DEFAULT; 048 049 public ValidationResult(FhirContext theCtx, List<SingleValidationMessage> theMessages) { 050 boolean successful = true; 051 myCtx = theCtx; 052 myMessages = theMessages; 053 for (SingleValidationMessage next : myMessages) { 054 if (next.getSeverity() == null || next.getSeverity().ordinal() > ResultSeverityEnum.WARNING.ordinal()) { 055 successful = false; 056 break; 057 } 058 } 059 myIsSuccessful = successful; 060 } 061 062 public List<SingleValidationMessage> getMessages() { 063 return Collections.unmodifiableList(myMessages); 064 } 065 066 /** 067 * Was the validation successful (in other words, do we have no issues that are at 068 * severity {@link ResultSeverityEnum#ERROR} or {@link ResultSeverityEnum#FATAL}. A validation 069 * is still considered successful if it only has issues at level {@link ResultSeverityEnum#WARNING} or 070 * lower. 071 * 072 * @return true if the validation was successful 073 */ 074 public boolean isSuccessful() { 075 return myIsSuccessful; 076 } 077 078 079 private String toDescription() { 080 if (myMessages.isEmpty()) { 081 return "No issues"; 082 } 083 084 StringBuilder b = new StringBuilder(100 * myMessages.size()); 085 int shownMsgQty = Math.min(myErrorDisplayLimit, myMessages.size()); 086 087 if (shownMsgQty < myMessages.size()) { 088 b.append("(showing first ").append(shownMsgQty).append(" messages out of ") 089 .append(myMessages.size()).append(" total)").append(ourNewLine); 090 } 091 092 for (int i = 0; i < shownMsgQty; i++) { 093 SingleValidationMessage nextMsg = myMessages.get(i); 094 b.append(ourNewLine); 095 if (nextMsg.getSeverity() != null) { 096 b.append(nextMsg.getSeverity().name()); 097 b.append(" - "); 098 } 099 b.append(nextMsg.getMessage()); 100 b.append(" - "); 101 b.append(nextMsg.getLocationString()); 102 } 103 104 return b.toString(); 105 } 106 107 108 /** 109 * @deprecated Use {@link #toOperationOutcome()} instead since this method returns a view. 110 * {@link #toOperationOutcome()} is identical to this method, but has a more suitable name so this method 111 * will be removed at some point. 112 */ 113 @Deprecated 114 public IBaseOperationOutcome getOperationOutcome() { 115 return toOperationOutcome(); 116 } 117 118 /** 119 * Create an OperationOutcome resource which contains all of the messages found as a result of this validation 120 */ 121 public IBaseOperationOutcome toOperationOutcome() { 122 IBaseOperationOutcome oo = (IBaseOperationOutcome) myCtx.getResourceDefinition("OperationOutcome").newInstance(); 123 populateOperationOutcome(oo); 124 return oo; 125 } 126 127 /** 128 * Populate an operation outcome with the results of the validation 129 */ 130 public void populateOperationOutcome(IBaseOperationOutcome theOperationOutcome) { 131 for (SingleValidationMessage next : myMessages) { 132 String location; 133 if (isNotBlank(next.getLocationString())) { 134 location = next.getLocationString(); 135 } else if (next.getLocationLine() != null || next.getLocationCol() != null) { 136 location = "Line[" + next.getLocationLine() + "] Col[" + next.getLocationCol() + "]"; 137 } else { 138 location = null; 139 } 140 String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null; 141 IBase issue = OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING); 142 143 if (next.getLocationLine() != null || next.getLocationCol() != null) { 144 String unknown = "(unknown)"; 145 String line = unknown; 146 if (next.getLocationLine() != null && next.getLocationLine() != -1) { 147 line = next.getLocationLine().toString(); 148 } 149 String col = unknown; 150 if (next.getLocationCol() != null && next.getLocationCol() != -1) { 151 col = next.getLocationCol().toString(); 152 } 153 if (!unknown.equals(line) || !unknown.equals(col)) { 154 OperationOutcomeUtil.addLocationToIssue(myCtx, issue, "Line " + line + ", Col " + col); 155 } 156 } 157 } 158 159 if (myMessages.isEmpty()) { 160 String message = myCtx.getLocalizer().getMessage(ValidationResult.class, "noIssuesDetected"); 161 OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, "information", message, null, "informational"); 162 } 163 } 164 165 @Override 166 public String toString() { 167 return "ValidationResult{" + "messageCount=" + myMessages.size() + ", isSuccessful=" + myIsSuccessful + ", description='" + toDescription() + '\'' + '}'; 168 } 169 170 /** 171 * @since 5.5.0 172 */ 173 public FhirContext getContext() { 174 return myCtx; 175 } 176 177 public int getErrorDisplayLimit() { return myErrorDisplayLimit; } 178 179 public void setErrorDisplayLimit(int theErrorDisplayLimit) { myErrorDisplayLimit = theErrorDisplayLimit; } 180 181 182 private static final String ourNewLine = System.getProperty("line.separator"); 183}