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}