001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2025 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.i18n.Msg; 024import ca.uhn.fhir.parser.IParser; 025import ca.uhn.fhir.parser.LenientErrorHandler; 026import ca.uhn.fhir.rest.api.EncodingEnum; 027import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 028import ca.uhn.fhir.util.ObjectUtil; 029import ca.uhn.fhir.util.ResourceUtil; 030import jakarta.annotation.Nonnull; 031import org.apache.commons.lang3.ObjectUtils; 032import org.hl7.fhir.instance.model.api.IBaseResource; 033 034import java.util.ArrayList; 035import java.util.List; 036 037import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; 038 039public class ValidationContext<T> extends BaseValidationContext<T> implements IValidationContext<T> { 040 041 private final IEncoder myEncoder; 042 private final T myResource; 043 private final EncodingEnum myResourceAsStringEncoding; 044 private final ValidationOptions myOptions; 045 private String myResourceAsString; 046 047 private ValidationContext( 048 FhirContext theContext, T theResource, IEncoder theEncoder, ValidationOptions theOptions) { 049 this(theContext, theResource, theEncoder, new ArrayList<>(), theOptions); 050 } 051 052 private ValidationContext( 053 FhirContext theContext, 054 T theResource, 055 IEncoder theEncoder, 056 List<SingleValidationMessage> theMessages, 057 ValidationOptions theOptions) { 058 super(theContext, theMessages); 059 myResource = theResource; 060 myEncoder = theEncoder; 061 myOptions = theOptions; 062 if (theEncoder != null) { 063 myResourceAsStringEncoding = theEncoder.getEncoding(); 064 } else { 065 myResourceAsStringEncoding = null; 066 } 067 } 068 069 @Override 070 public T getResource() { 071 return myResource; 072 } 073 074 @Override 075 public String getResourceAsString() { 076 if (myResourceAsString == null) { 077 myResourceAsString = myEncoder.encode(); 078 } 079 return myResourceAsString; 080 } 081 082 @Override 083 public EncodingEnum getResourceAsStringEncoding() { 084 return myResourceAsStringEncoding; 085 } 086 087 @Nonnull 088 @Override 089 public ValidationOptions getOptions() { 090 return myOptions; 091 } 092 093 private interface IEncoder { 094 String encode(); 095 096 EncodingEnum getEncoding(); 097 } 098 099 public static <T extends IBaseResource> IValidationContext<T> forResource( 100 final FhirContext theContext, final T theResource, ValidationOptions theOptions) { 101 ObjectUtil.requireNonNull(theContext, "theContext can not be null"); 102 ObjectUtil.requireNonNull(theResource, "theResource can not be null"); 103 ValidationOptions options = defaultIfNull(theOptions, ValidationOptions.empty()); 104 105 IEncoder encoder = new IEncoder() { 106 @Override 107 public String encode() { 108 // use the stored json string, if available 109 // otherwise, encode the actual resource 110 return ObjectUtils.firstNonNull( 111 ResourceUtil.getRawStringFromResourceOrNull(theResource), 112 theContext.newJsonParser().encodeResourceToString(theResource)); 113 } 114 115 @Override 116 public EncodingEnum getEncoding() { 117 return ObjectUtils.defaultIfNull( 118 ResourceUtil.getEncodingTypeFromUserData(theResource), EncodingEnum.JSON); 119 } 120 }; 121 return new ValidationContext<>(theContext, theResource, encoder, options); 122 } 123 124 public static IValidationContext<IBaseResource> forText( 125 final FhirContext theContext, final String theResourceBody, final ValidationOptions theOptions) { 126 ObjectUtil.requireNonNull(theContext, "theContext can not be null"); 127 ObjectUtil.requireNotEmpty(theResourceBody, "theResourceBody can not be null or empty"); 128 ValidationOptions options = defaultIfNull(theOptions, ValidationOptions.empty()); 129 130 return new BaseValidationContext<IBaseResource>(theContext) { 131 132 private EncodingEnum myEncoding; 133 private IBaseResource myParsed; 134 135 @Override 136 public IBaseResource getResource() { 137 if (myParsed == null) { 138 IParser parser = getResourceAsStringEncoding().newParser(getFhirContext()); 139 LenientErrorHandler errorHandler = new LenientErrorHandler(); 140 errorHandler.setErrorOnInvalidValue(false); 141 parser.setParserErrorHandler(errorHandler); 142 myParsed = parser.parseResource(getResourceAsString()); 143 } 144 return myParsed; 145 } 146 147 @Override 148 public String getResourceAsString() { 149 return theResourceBody; 150 } 151 152 @Override 153 public EncodingEnum getResourceAsStringEncoding() { 154 if (myEncoding == null) { 155 myEncoding = EncodingEnum.detectEncodingNoDefault(theResourceBody); 156 if (myEncoding == null) { 157 throw new InvalidRequestException(Msg.code(1971) 158 + theContext 159 .getLocalizer() 160 .getMessage(ValidationContext.class, "unableToDetermineEncoding")); 161 } 162 } 163 return myEncoding; 164 } 165 166 @Nonnull 167 @Override 168 public ValidationOptions getOptions() { 169 return options; 170 } 171 }; 172 } 173 174 public static IValidationContext<IBaseResource> subContext( 175 final IValidationContext<IBaseResource> theCtx, 176 final IBaseResource theResource, 177 ValidationOptions theOptions) { 178 return new ValidationContext<>( 179 theCtx.getFhirContext(), 180 theResource, 181 new IEncoder() { 182 @Override 183 public String encode() { 184 return theCtx.getFhirContext().newXmlParser().encodeResourceToString(theResource); 185 } 186 187 @Override 188 public EncodingEnum getEncoding() { 189 return EncodingEnum.XML; 190 } 191 }, 192 theCtx.getMessages(), 193 theOptions); 194 } 195}