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}