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}