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.FhirContext;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.rest.api.EncodingEnum;
026import ca.uhn.fhir.util.BundleBuilder;
027import ca.uhn.fhir.util.BundleUtil;
028import org.hl7.fhir.instance.model.api.IBaseBundle;
029import org.hl7.fhir.instance.model.api.IBaseResource;
030
031import java.io.BufferedReader;
032import java.io.IOException;
033import java.io.Reader;
034import java.io.Writer;
035import java.util.List;
036
037
038/**
039 * This class is the FHIR NDJSON parser/encoder. Users should not interact with this class directly, but should use
040 * {@link FhirContext#newNDJsonParser()} to get an instance.
041 */
042public class NDJsonParser extends BaseParser {
043
044        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NDJsonParser.class);
045
046        private IParser myJsonParser;
047        private FhirContext myFhirContext;
048
049        /**
050         * Do not use this constructor, the recommended way to obtain a new instance of the NDJSON parser is to invoke
051         * {@link FhirContext#newNDJsonParser()}.
052         *
053         * @param theParserErrorHandler
054         */
055        public NDJsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
056                super(theContext, theParserErrorHandler);
057                myFhirContext = theContext;
058
059                myJsonParser = theContext.newJsonParser();
060        }
061
062        @Override
063        public IParser setPrettyPrint(boolean thePrettyPrint) {
064                myJsonParser.setPrettyPrint(thePrettyPrint);
065                return this;
066        }
067
068        @Override
069        public EncodingEnum getEncoding() {
070                return EncodingEnum.NDJSON;
071        }
072
073        @Override
074        protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException {
075                // We only encode bundles to NDJSON.
076                if (!(IBaseBundle.class.isAssignableFrom(theResource.getClass()))) {
077                        throw new IllegalArgumentException(Msg.code(1833) + "NDJsonParser can only encode Bundle types.  Received " + 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) throws DataFormatException {
098                // We can only parse to bundles.
099                if ((theResourceType != null) && (!(IBaseBundle.class.isAssignableFrom(theResourceType)))) {
100                        throw new DataFormatException(Msg.code(1834) + "NDJsonParser can only parse to Bundle types.  Received " + theResourceType.getName());
101                }
102
103                try {
104                        // Now we go through line-by-line parsing the JSON and then stuffing it into a bundle.
105                        BundleBuilder myBuilder = new BundleBuilder(myFhirContext);
106                        myBuilder.setType("collection");
107                        BufferedReader myBufferedReader = new BufferedReader(theReader);
108                        String jsonString = myBufferedReader.readLine();
109                        while (jsonString != null) {
110                                // And add it to a collection in a Bundle.
111                                // The string must be trimmed, as per the NDJson spec 3.2
112                                myBuilder.addCollectionEntry(myJsonParser.parseResource(jsonString.trim()));
113                                // Try to read another line.
114                                jsonString = myBufferedReader.readLine();
115                        }
116
117                        return (T) myBuilder.getBundle();
118                } catch (IOException err) {
119                        throw new DataFormatException(Msg.code(1835) + err.getMessage());
120                }
121        }
122}