View Javadoc
1   /*
2    * Copyright 2016 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.openehealth.ipf.platform.camel.ihe.fhir.core;
18  
19  import ca.uhn.fhir.context.FhirContext;
20  import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
21  import ca.uhn.fhir.validation.FhirValidator;
22  import ca.uhn.fhir.validation.ValidationResult;
23  import org.apache.camel.Exchange;
24  import org.apache.camel.Processor;
25  import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
26  import org.hl7.fhir.instance.model.api.IBaseResource;
27  import org.openehealth.ipf.commons.ihe.fhir.Constants;
28  import org.openehealth.ipf.commons.ihe.fhir.FhirInteractionId;
29  import org.openehealth.ipf.commons.ihe.fhir.FhirTransactionValidator;
30  import org.openehealth.ipf.platform.camel.core.adapter.ValidatorAdapter;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import static org.openehealth.ipf.commons.ihe.core.Constants.INTERACTION_ID_NAME;
35  
36  /**
37   * <p>
38   * Factory class for creating validating processors for FHIR resources. For
39   * schematron validation you need to have the phloc-schematron library in
40   * the classpath.
41   * </p>
42   * <p>
43   * The FHIR validators are used as processors, e.g.:
44   * <pre>
45   *     ...
46   *     .process(FhirResourceValidators.itiRequestValidator())
47   *     ...
48   * </pre>
49   * An instance of {@link UnprocessableEntityException} is thrown if validation
50   * fails. A FHIR Consumer will automatically process the exception for a 400
51   * response.
52   * </p>
53   * <p>
54   * Validation is executed depending on the presence of {@link FhirContext} in the message header.
55   * The kind of validation is determined by the integer value in the {@link #VALIDATION_MODE}
56   * message header, i.e. which of the following bits are set:
57   * <ul>
58   * <li>header not present : schema validation</li>
59   * <li>{@link FhirCamelValidators#OFF} (0)        : no validation</li>
60   * <li>{@link FhirCamelValidators#SCHEMA} (1)     : schema validation</li>
61   * <li>{@link FhirCamelValidators#SCHEMATRON} (2) : schematron validation</li>
62   * <li>{@link FhirCamelValidators#MODEL} (4)      : resource model validation</li>
63   * </ul>
64   * </p>
65   * <p>
66   * Higher value result in an higher performance impact due to the validation. Model-based validation can slow down
67   * processing significantly.
68   * </p>
69   *
70   * @author Christian Ohr
71   * @since 3.2
72   */
73  public final class FhirCamelValidators {
74  
75      private static final Logger LOG = LoggerFactory.getLogger(FhirCamelValidators.class);
76  
77      private FhirCamelValidators() {
78      }
79  
80      public static final String VALIDATION_MODE = "fhir.validation.mode";
81  
82      public static final int MODEL = 4;
83      public static final int SCHEMATRON = 2;
84      public static final int SCHEMA = 1;
85      public static final int OFF = 0;
86  
87      static boolean isValidateSchema(int actualMode) {
88          return (actualMode & SCHEMA) == SCHEMA;
89      }
90  
91      static boolean isValidateSchematron(int actualMode) {
92          return (actualMode & SCHEMATRON) == SCHEMATRON;
93      }
94  
95      static boolean isValidateModel(int actualMode) {
96          return (actualMode & MODEL) == MODEL;
97      }
98  
99      /**
100      * @return instance of FHIR validator processor that checks the FHIR resource
101      * belonging to the request of the FHIR transaction
102      */
103     public static Processor itiRequestValidator() {
104         return exchange -> {
105             FhirContext context = exchange.getIn().getHeader(Constants.FHIR_CONTEXT, FhirContext.class);
106             if (context != null) {
107              if (exchange.getIn().getBody() instanceof IBaseResource) {
108                  if (ValidatorAdapter.validationEnabled(exchange)) {
109                      Integer mode = exchange.getIn().getHeader(VALIDATION_MODE, Integer.class);
110                      if (mode == null) mode = SCHEMA;
111                      if (isValidateSchema(mode) || isValidateSchematron(mode)) {
112                          validate(exchange, context, isValidateSchema(mode), isValidateSchematron(mode));
113                      }
114                      if (isValidateModel(mode)) {
115                          FhirInteractionId fhirInteractionId = exchange.getIn().getHeader(INTERACTION_ID_NAME, FhirInteractionId.class);
116                          if (fhirInteractionId != null) {
117                              FhirTransactionValidator validator = fhirInteractionId.getFhirTransactionConfiguration().getFhirValidator();
118                              validator.validateRequest(context, exchange.getIn().getBody(), exchange.getIn().getHeaders());
119                          } else {
120                              LOG.warn("Could not validate request because FHIR Transaction ID is unknown");
121                          }
122                      }
123                  }
124              }
125             } else {
126                 LOG.warn("Could not validate request because FHIR Context is unknown");
127             }
128         };
129     }
130 
131     /**
132      * @return instance of FHIR validator processor that checks the FHIR resource
133      * belonging to the response of the FHIR transaction
134      */
135     public static Processor itiResponseValidator() {
136         return exchange -> {
137             FhirContext context = exchange.getIn().getHeader(Constants.FHIR_CONTEXT, FhirContext.class);
138             if (context != null && exchange.getIn().getBody() instanceof IBaseResource) {
139                 int mode = exchange.getIn().getHeader(VALIDATION_MODE, Integer.class);
140                 if (isValidateSchema(mode) || isValidateSchematron(mode)) {
141                     validate(exchange, context, isValidateSchema(mode), isValidateSchematron(mode));
142                 }
143                 if (isValidateModel(mode)) {
144                     FhirInteractionId fhirInteractionId = exchange.getIn().getHeader(INTERACTION_ID_NAME, FhirInteractionId.class);
145                     if (fhirInteractionId != null) {
146                         FhirTransactionValidator validator = fhirInteractionId.getFhirTransactionConfiguration().getFhirValidator();
147                         validator.validateResponse(context, exchange.getIn().getBody(), exchange.getIn().getHeaders());
148                     } else {
149                         LOG.warn("Could not validate request because FHIR Transaction ID is unknown");
150                     }
151                 }
152             } else {
153                 LOG.warn("Could not validate request because FHIR Context is unknown");
154             }
155         };
156     }
157 
158     private static void validate(Exchange exchange, FhirContext context, boolean checkSchema, boolean checkSchematron) {
159         ValidationResult result = getValidator(context, checkSchema, checkSchematron)
160                 .validateWithResult(exchange.getIn().getBody(IBaseResource.class));
161         if (!result.isSuccessful()) {
162             IBaseOperationOutcome outcome = result.toOperationOutcome();
163             LOG.debug("FHIR Validation failed with outcome {}", outcome);
164             throw new UnprocessableEntityException("FHIR Validation Error", outcome);
165         } else {
166             LOG.debug("FHIR Validation succeeded");
167         }
168     }
169 
170     private static FhirValidator getValidator(FhirContext context, boolean checkSchema, boolean checkSchematron) {
171         FhirValidator validator = context.newValidator();
172         validator.setValidateAgainstStandardSchema(checkSchema);
173         // Bug in HAPI: setting to false already looks up classpath for schematron lib
174         if (checkSchematron) validator.setValidateAgainstStandardSchematron(true);
175         return validator;
176     }
177 }