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.util;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.model.api.IModelJson;
024import ca.uhn.fhir.model.api.annotation.SensitiveNoDisplay;
025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
026import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
027import com.fasterxml.jackson.annotation.JsonInclude;
028import com.fasterxml.jackson.core.JsonGenerator;
029import com.fasterxml.jackson.core.JsonProcessingException;
030import com.fasterxml.jackson.databind.ObjectMapper;
031import com.fasterxml.jackson.databind.SerializationFeature;
032import com.fasterxml.jackson.databind.SerializerProvider;
033import com.fasterxml.jackson.databind.ser.FilterProvider;
034import com.fasterxml.jackson.databind.ser.PropertyWriter;
035import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
036import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
037import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
038import jakarta.annotation.Nonnull;
039
040import java.io.IOException;
041import java.io.InputStream;
042import java.io.StringWriter;
043import java.io.Writer;
044import java.util.List;
045
046public class JsonUtil {
047
048        private static final ObjectMapper ourMapperPrettyPrint;
049        private static final ObjectMapper ourMapperNonPrettyPrint;
050        private static final ObjectMapper ourMapperIncludeSensitive;
051
052        public static final SimpleBeanPropertyFilter SIMPLE_BEAN_PROPERTY_FILTER = new SensitiveDataFilter();
053
054        public static final SimpleFilterProvider SENSITIVE_DATA_FILTER_PROVIDER =
055                        new SimpleFilterProvider().addFilter(IModelJson.SENSITIVE_DATA_FILTER_NAME, SIMPLE_BEAN_PROPERTY_FILTER);
056        public static final SimpleFilterProvider SHOW_ALL_DATA_FILTER_PROVIDER = new SimpleFilterProvider()
057                        .addFilter(IModelJson.SENSITIVE_DATA_FILTER_NAME, SimpleBeanPropertyFilter.serializeAll());
058
059        static {
060                ourMapperPrettyPrint = new ObjectMapper();
061                ourMapperPrettyPrint.setSerializationInclusion(JsonInclude.Include.NON_NULL);
062                ourMapperPrettyPrint.setFilterProvider(SENSITIVE_DATA_FILTER_PROVIDER);
063                ourMapperPrettyPrint.enable(SerializationFeature.INDENT_OUTPUT);
064                // Needed to handle ZonedDateTime
065                ourMapperPrettyPrint.registerModule(new JavaTimeModule());
066
067                ourMapperNonPrettyPrint = new ObjectMapper();
068                ourMapperNonPrettyPrint.setSerializationInclusion(JsonInclude.Include.NON_NULL);
069                ourMapperNonPrettyPrint.setFilterProvider(SENSITIVE_DATA_FILTER_PROVIDER);
070                ourMapperNonPrettyPrint.disable(SerializationFeature.INDENT_OUTPUT);
071                // Needed to handle ZonedDateTime
072                ourMapperNonPrettyPrint.registerModule(new JavaTimeModule());
073
074                ourMapperIncludeSensitive = new ObjectMapper();
075                ourMapperIncludeSensitive.setFilterProvider(SHOW_ALL_DATA_FILTER_PROVIDER);
076                ourMapperIncludeSensitive.setSerializationInclusion(JsonInclude.Include.NON_NULL);
077                ourMapperIncludeSensitive.disable(SerializationFeature.INDENT_OUTPUT);
078                // Needed to handle ZonedDateTime
079                ourMapperIncludeSensitive.registerModule(new JavaTimeModule());
080        }
081
082        /**
083         * Parse JSON
084         */
085        public static <T> T deserialize(@Nonnull String theInput, @Nonnull Class<T> theType) {
086                try {
087                        return ourMapperPrettyPrint.readerFor(theType).readValue(theInput);
088                } catch (IOException e) {
089                        // Should not happen
090                        throw new InternalErrorException(Msg.code(2060) + e);
091                }
092        }
093
094        /**
095         * Parse JSON
096         */
097        public static <T> List<T> deserializeList(@Nonnull String theInput, @Nonnull Class<T> theType) throws IOException {
098                return ourMapperPrettyPrint.readerForListOf(theType).readValue(theInput);
099        }
100        /**
101         * Parse JSON
102         */
103        public static <T> T deserialize(@Nonnull InputStream theInput, @Nonnull Class<T> theType) throws IOException {
104                return ourMapperPrettyPrint.readerFor(theType).readValue(theInput);
105        }
106
107        /**
108         * Includes fields which are annotated with {@link SensitiveNoDisplay}. Currently only meant to be used for serialization
109         * for batch job parameters.
110         */
111        public static String serializeWithSensitiveData(@Nonnull IModelJson theInput) {
112                try {
113                        return ourMapperIncludeSensitive.writeValueAsString(theInput);
114                } catch (JsonProcessingException e) {
115                        throw new InvalidRequestException(Msg.code(2487) + "Failed to encode " + theInput.getClass(), e);
116                }
117        }
118
119        /**
120         * Encode JSON
121         */
122        public static String serialize(@Nonnull Object theInput) {
123                return serialize(theInput, true);
124        }
125
126        /**
127         * Encode JSON
128         */
129        public static String serialize(@Nonnull Object theInput, boolean thePrettyPrint) {
130                try {
131                        StringWriter sw = new StringWriter();
132                        if (thePrettyPrint) {
133                                ourMapperPrettyPrint.writeValue(sw, theInput);
134                        } else {
135                                ourMapperNonPrettyPrint.writeValue(sw, theInput);
136                        }
137                        return sw.toString();
138                } catch (IOException e) {
139                        // Should not happen
140                        throw new InternalErrorException(Msg.code(2061) + e);
141                }
142        }
143
144        public FilterProvider getSensitiveDataFilterProvider() {
145                return SENSITIVE_DATA_FILTER_PROVIDER;
146        }
147
148        /**
149         * Encode JSON
150         */
151        public static void serialize(@Nonnull Object theInput, @Nonnull Writer theWriter) throws IOException {
152                // Note: We append a string here rather than just having ourMapper write directly
153                // to the Writer because ourMapper seems to close the writer for some stupid
154                // reason.. There's probably a way of preventing that bit I'm not sure what that
155                // is and it's not a big deal here.
156                theWriter.append(serialize(theInput));
157        }
158
159        public static String serializeOrInvalidRequest(IModelJson theJson) {
160                try {
161                        return ourMapperNonPrettyPrint.writeValueAsString(theJson);
162                } catch (JsonProcessingException e) {
163                        throw new InvalidRequestException(Msg.code(1741) + "Failed to encode " + theJson.getClass(), e);
164                }
165        }
166
167        private static class SensitiveDataFilter extends SimpleBeanPropertyFilter {
168
169                @Override
170                protected boolean include(PropertyWriter writer) {
171                        return true; // Default include all except explicitly checked and excluded
172                }
173
174                @Override
175                public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider provider, PropertyWriter writer)
176                                throws Exception {
177                        if (include(writer)) {
178                                if (!isFieldSensitive(writer)) {
179                                        super.serializeAsField(pojo, gen, provider, writer);
180                                }
181                        }
182                }
183
184                private boolean isFieldSensitive(PropertyWriter writer) {
185                        return writer.getAnnotation(SensitiveNoDisplay.class) != null;
186                }
187        }
188}