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