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}