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