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