001package ca.uhn.fhir.parser;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
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 ca.uhn.fhir.context.*;
024import ca.uhn.fhir.rest.api.EncodingEnum;
025import ca.uhn.fhir.util.BundleBuilder;
026import ca.uhn.fhir.util.BundleUtil;
027import org.hl7.fhir.instance.model.api.*;
028
029import java.io.IOException;
030import java.io.BufferedReader;
031import java.io.Reader;
032import java.io.Writer;
033import java.util.List;
034
035
036/**
037 * This class is the FHIR NDJSON parser/encoder. Users should not interact with this class directly, but should use
038 * {@link FhirContext#newNDJsonParser()} to get an instance.
039 */
040public class NDJsonParser extends BaseParser {
041
042        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NDJsonParser.class);
043
044        private IParser myJsonParser;
045        private FhirContext myFhirContext;
046
047        /**
048         * Do not use this constructor, the recommended way to obtain a new instance of the NDJSON parser is to invoke
049         * {@link FhirContext#newNDJsonParser()}.
050         *
051         * @param theParserErrorHandler
052         */
053        public NDJsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
054                super(theContext, theParserErrorHandler);
055                myFhirContext = theContext;
056
057                myJsonParser = theContext.newJsonParser();
058        }
059
060        @Override
061        public IParser setPrettyPrint(boolean thePrettyPrint) {
062                myJsonParser.setPrettyPrint(thePrettyPrint);
063                return this;
064        }
065
066        @Override
067        public EncodingEnum getEncoding() {
068                return EncodingEnum.NDJSON;
069        }
070
071        @Override
072        protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
073                // We only encode bundles to NDJSON.
074                if (!(IBaseBundle.class.isAssignableFrom(theResource.getClass()))) {
075                        throw new IllegalArgumentException("NDJsonParser can only encode Bundle types.  Received " + theResource.getClass().getName());
076                }
077
078                // Ok, convert the bundle to a list of resources.
079                List<IBaseResource> theBundleResources = BundleUtil.toListOfResources(myFhirContext, (IBaseBundle) theResource);
080
081                // Now we write each one in turn.
082                // Use newline only as a line separator, not at the end of the file.
083                boolean isFirstResource = true;
084                for (IBaseResource theBundleEntryResource : theBundleResources) {
085                        if (!(isFirstResource)) {
086                                theWriter.write("\n");
087                        }
088                        isFirstResource = false;
089
090                        myJsonParser.encodeResourceToWriter(theBundleEntryResource, theWriter);
091                }
092        }
093
094        @Override
095        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
096                // We can only parse to bundles.
097                if ((theResourceType != null) && (!(IBaseBundle.class.isAssignableFrom(theResourceType)))) {
098                        throw new DataFormatException("NDJsonParser can only parse to Bundle types.  Received " + theResourceType.getName());
099                }
100
101                try {
102                        // Now we go through line-by-line parsing the JSON and then stuffing it into a bundle.
103                        BundleBuilder myBuilder = new BundleBuilder(myFhirContext);
104                        myBuilder.setType("collection");
105                        BufferedReader myBufferedReader = new BufferedReader(theReader);
106                        String jsonString = myBufferedReader.readLine();
107                        while (jsonString != null) {
108                                // And add it to a collection in a Bundle.
109                                // The string must be trimmed, as per the NDJson spec 3.2
110                                myBuilder.addCollectionEntry(myJsonParser.parseResource(jsonString.trim()));
111                                // Try to read another line.
112                                jsonString = myBufferedReader.readLine();
113                        }
114
115                        return (T) myBuilder.getBundle();
116                } catch (IOException err) {
117                        throw new DataFormatException(err.getMessage());
118                }
119        }
120}