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