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