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.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 * 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)
073                        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 "
077                                        + theResource.getClass().getName());
078                }
079
080                // Ok, convert the bundle to a list of resources.
081                List<IBaseResource> theBundleResources = BundleUtil.toListOfResources(myFhirContext, (IBaseBundle) theResource);
082
083                // Now we write each one in turn.
084                // Use newline only as a line separator, not at the end of the file.
085                boolean isFirstResource = true;
086                for (IBaseResource theBundleEntryResource : theBundleResources) {
087                        if (!(isFirstResource)) {
088                                theWriter.write("\n");
089                        }
090                        isFirstResource = false;
091
092                        myJsonParser.encodeResourceToWriter(theBundleEntryResource, theWriter);
093                }
094        }
095
096        @Override
097        public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader)
098                        throws DataFormatException {
099                // We can only parse to bundles.
100                if ((theResourceType != null) && (!(IBaseBundle.class.isAssignableFrom(theResourceType)))) {
101                        throw new DataFormatException(Msg.code(1834) + "NDJsonParser can only parse to Bundle types.  Received "
102                                        + theResourceType.getName());
103                }
104
105                try {
106                        // Now we go through line-by-line parsing the JSON and then stuffing it into a bundle.
107                        BundleBuilder myBuilder = new BundleBuilder(myFhirContext);
108                        myBuilder.setType("collection");
109                        BufferedReader myBufferedReader = new BufferedReader(theReader);
110                        String jsonString = myBufferedReader.readLine();
111                        while (jsonString != null) {
112                                // And add it to a collection in a Bundle.
113                                // The string must be trimmed, as per the NDJson spec 3.2
114                                myBuilder.addCollectionEntry(myJsonParser.parseResource(jsonString.trim()));
115                                // Try to read another line.
116                                jsonString = myBufferedReader.readLine();
117                        }
118
119                        return (T) myBuilder.getBundle();
120                } catch (IOException err) {
121                        throw new DataFormatException(Msg.code(1835) + err.getMessage());
122                }
123        }
124}