001package ca.uhn.fhir.validation; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2020 University Health Network 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 static org.apache.commons.lang3.StringUtils.isNotBlank; 024 025import java.util.Collections; 026import java.util.List; 027 028import org.hl7.fhir.instance.model.api.IBase; 029import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; 030 031import ca.uhn.fhir.context.FhirContext; 032import ca.uhn.fhir.rest.api.Constants; 033import ca.uhn.fhir.util.OperationOutcomeUtil; 034 035/** 036 * Encapsulates the results of validation 037 * 038 * @see ca.uhn.fhir.validation.FhirValidator 039 * @since 0.7 040 */ 041public class ValidationResult { 042 private final FhirContext myCtx; 043 private final boolean myIsSuccessful; 044 private final List<SingleValidationMessage> myMessages; 045 046 public ValidationResult(FhirContext theCtx, List<SingleValidationMessage> theMessages) { 047 boolean successful = true; 048 myCtx = theCtx; 049 myMessages = theMessages; 050 for (SingleValidationMessage next : myMessages) { 051 next.getSeverity(); 052 if (next.getSeverity() == null || next.getSeverity().ordinal() > ResultSeverityEnum.WARNING.ordinal()) { 053 successful = false; 054 } 055 } 056 myIsSuccessful = successful; 057 } 058 059 public List<SingleValidationMessage> getMessages() { 060 return Collections.unmodifiableList(myMessages); 061 } 062 063 /** 064 * Was the validation successful (in other words, do we have no issues that are at 065 * severity {@link ResultSeverityEnum#ERROR} or {@link ResultSeverityEnum#FATAL}. A validation 066 * is still considered successful if it only has issues at level {@link ResultSeverityEnum#WARNING} or 067 * lower. 068 * 069 * @return true if the validation was successful 070 */ 071 public boolean isSuccessful() { 072 return myIsSuccessful; 073 } 074 075 private String toDescription() { 076 StringBuilder b = new StringBuilder(100); 077 if (myMessages.size() > 0) { 078 if (myMessages.get(0).getSeverity() != null) { 079 b.append(myMessages.get(0).getSeverity().name()); 080 b.append(" - "); 081 } 082 b.append(myMessages.get(0).getMessage()); 083 b.append(" - "); 084 b.append(myMessages.get(0).getLocationString()); 085 } else { 086 b.append("No issues"); 087 } 088 return b.toString(); 089 } 090 091 /** 092 * @deprecated Use {@link #toOperationOutcome()} instead since this method returns a view. 093 * {@link #toOperationOutcome()} is identical to this method, but has a more suitable name so this method 094 * will be removed at some point. 095 */ 096 @Deprecated 097 public IBaseOperationOutcome getOperationOutcome() { 098 return toOperationOutcome(); 099 } 100 101 /** 102 * Create an OperationOutcome resource which contains all of the messages found as a result of this validation 103 */ 104 public IBaseOperationOutcome toOperationOutcome() { 105 IBaseOperationOutcome oo = (IBaseOperationOutcome) myCtx.getResourceDefinition("OperationOutcome").newInstance(); 106 populateOperationOutcome(oo); 107 return oo; 108 } 109 110 /** 111 * Populate an operation outcome with the results of the validation 112 */ 113 public void populateOperationOutcome(IBaseOperationOutcome theOperationOutcome) { 114 for (SingleValidationMessage next : myMessages) { 115 String location; 116 if (isNotBlank(next.getLocationString())) { 117 location = next.getLocationString(); 118 } else if (next.getLocationLine() != null || next.getLocationCol() != null) { 119 location = "Line[" + next.getLocationLine() + "] Col[" + next.getLocationCol() + "]"; 120 } else { 121 location = null; 122 } 123 String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null; 124 IBase issue = OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, Constants.OO_INFOSTATUS_PROCESSING); 125 126 if (next.getLocationLine() != null || next.getLocationCol() != null) { 127 String unknown = "(unknown)"; 128 String line = unknown; 129 if (next.getLocationLine() != null && next.getLocationLine() != -1) { 130 line = next.getLocationLine().toString(); 131 } 132 String col = unknown; 133 if (next.getLocationCol() != null && next.getLocationCol() != -1) { 134 col = next.getLocationCol().toString(); 135 } 136 if (!unknown.equals(line) || !unknown.equals(col)) { 137 OperationOutcomeUtil.addLocationToIssue(myCtx, issue, "Line " + line + ", Col " + col); 138 } 139 } 140 } 141 142 if (myMessages.isEmpty()) { 143 String message = myCtx.getLocalizer().getMessage(ValidationResult.class, "noIssuesDetected"); 144 OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, "information", message, null, "informational"); 145 } 146 } 147 148 @Override 149 public String toString() { 150 return "ValidationResult{" + "messageCount=" + myMessages.size() + ", isSuccessful=" + myIsSuccessful + ", description='" + toDescription() + '\'' + '}'; 151 } 152}