
001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 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.rest.server.interceptor.validation; 021 022import ca.uhn.fhir.interceptor.api.Hook; 023import ca.uhn.fhir.interceptor.api.Interceptor; 024import ca.uhn.fhir.interceptor.api.Pointcut; 025import ca.uhn.fhir.validation.SingleValidationMessage; 026import ca.uhn.fhir.validation.ValidationResult; 027import jakarta.annotation.Nonnull; 028 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collections; 032import java.util.List; 033import java.util.regex.Pattern; 034 035import static org.apache.commons.lang3.ObjectUtils.isEmpty; 036import static org.apache.commons.lang3.StringUtils.isNotBlank; 037 038@Interceptor 039public class ValidationMessageSuppressingInterceptor { 040 041 private List<Pattern> mySuppressPatterns = new ArrayList<>(); 042 043 /** 044 * Constructor 045 */ 046 public ValidationMessageSuppressingInterceptor() { 047 super(); 048 } 049 050 /** 051 * Supplies one or more patterns to suppress. Any validation messages (of any severity) will be suppressed 052 * if they match this pattern. Patterns are in Java Regular Expression format (as defined by the {@link Pattern} class) 053 * and are treated as partial maches. They are also case insensitive. 054 * <p> 055 * For example, a pattern of <code>loinc.*1234</code> would suppress the following message:<br/> 056 * <code>The LOINC code 1234 is not valid</code> 057 * </p> 058 */ 059 public ValidationMessageSuppressingInterceptor addMessageSuppressionPatterns(String... thePatterns) { 060 return addMessageSuppressionPatterns(Arrays.asList(thePatterns)); 061 } 062 063 /** 064 * Supplies one or more patterns to suppress. Any validation messages (of any severity) will be suppressed 065 * if they match this pattern. Patterns are in Java Regular Expression format (as defined by the {@link Pattern} class) 066 * and are treated as partial maches. They are also case insensitive. 067 * <p> 068 * For example, a pattern of <code>loinc.*1234</code> would suppress the following message:<br/> 069 * <code>The LOINC code 1234 is not valid</code> 070 * </p> 071 */ 072 public ValidationMessageSuppressingInterceptor addMessageSuppressionPatterns(List<String> thePatterns) { 073 for (String next : thePatterns) { 074 if (isNotBlank(next)) { 075 Pattern pattern = Pattern.compile(next, Pattern.CASE_INSENSITIVE); 076 mySuppressPatterns.add(pattern); 077 } 078 } 079 return this; 080 } 081 082 @Hook(Pointcut.VALIDATION_COMPLETED) 083 public ValidationResult handle(ValidationResult theResult) { 084 085 List<SingleValidationMessage> originalMessages = theResult.getMessages(); 086 List<SingleValidationMessage> newMessages = new ArrayList<>(originalMessages.size()); 087 088 for (SingleValidationMessage message : originalMessages) { 089 boolean shouldSuppress = false; 090 091 for (Pattern nextSuppressPattern : mySuppressPatterns) { 092 093 if (message.hasSliceMessages()) { 094 List<String> sliceMessages = message.getSliceMessages(); 095 List<String> filteredSliceMessages = filterSliceMessages(nextSuppressPattern, sliceMessages); 096 message.setSliceMessages(filteredSliceMessages); 097 098 if (isEmpty(filteredSliceMessages)) { 099 // all slice messages were suppressed, we should suppress the entire SingleValidationMessage 100 shouldSuppress = true; 101 break; 102 } 103 } else { 104 String nextMessage = message.getMessage(); 105 if (nextSuppressPattern.matcher(nextMessage).find()) { 106 shouldSuppress = true; 107 break; 108 } 109 } 110 } 111 112 if (!shouldSuppress) { 113 newMessages.add(message); 114 } 115 } 116 117 theResult.setMessages(newMessages); 118 119 return null; // keep processing 120 } 121 122 private List<String> filterSliceMessages( 123 @Nonnull Pattern thePattern, @Nonnull List<String> theOriginalSliceMessages) { 124 if (isEmpty(theOriginalSliceMessages)) { 125 return Collections.emptyList(); 126 } 127 128 List<String> filteredSliceMessages = new ArrayList<>(); 129 for (String sliceMessage : theOriginalSliceMessages) { 130 boolean shouldSuppress = thePattern.matcher(sliceMessage).find(); 131 if (!shouldSuppress) { 132 filteredSliceMessages.add(sliceMessage); 133 } 134 } 135 136 return filteredSliceMessages; 137 } 138}