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}