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.parser.StrictErrorHandler;
21  import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
22  import ca.uhn.fhir.rest.gclient.IClientExecutable;
23  import lombok.Getter;
24  import org.apache.camel.spi.UriParam;
25  import org.apache.camel.spi.UriParams;
26  import org.apache.camel.util.EndpointHelper;
27  import org.apache.camel.util.jsse.SSLContextParameters;
28  import org.openehealth.ipf.commons.ihe.fhir.AbstractPlainProvider;
29  import org.openehealth.ipf.commons.ihe.fhir.ClientRequestFactory;
30  import org.openehealth.ipf.commons.ihe.fhir.IpfFhirServlet;
31  import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDataset;
32  import org.openehealth.ipf.commons.ihe.fhir.translation.FhirSecurityInformation;
33  import org.openehealth.ipf.platform.camel.ihe.atna.AuditableEndpointConfiguration;
34  import org.openehealth.ipf.platform.camel.ihe.core.AmbiguousBeanException;
35  
36  import javax.net.ssl.HostnameVerifier;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.stream.Collectors;
40  import java.util.stream.Stream;
41  
42  /**
43   * Configuration of a FHIR endpoint instance
44   *
45   * @author Christian Ohr
46   * @since 3.1
47   */
48  @UriParams
49  public class FhirEndpointConfiguration<AuditDatasetType extends FhirAuditDataset> extends AuditableEndpointConfiguration {
50  
51      static final String STRICT = "strict";
52      static final String LENIENT = "lenient";
53      static final String LAZY_LOAD_BUNDLES = "lazyLoadBundles";
54      static final String CACHE_BUNDLES = "cacheBundles";
55  
56      @Getter
57      private final String path;
58  
59      @Getter
60      private FhirContext context;
61  
62      @Getter
63      @UriParam(defaultValue = "FhirServlet")
64      private String servletName = IpfFhirServlet.DEFAULT_SERVLET_NAME;
65  
66      @Getter
67      @UriParam
68      private List<? extends AbstractPlainProvider> resourceProvider;
69  
70      // Producer only
71  
72      @UriParam
73      private ClientRequestFactory<? extends IClientExecutable<?, ?>> clientRequestFactory;
74  
75  
76      @Getter
77      @UriParam
78      private List<HapiClientInterceptorFactory> hapiClientInterceptorFactories;
79  
80      @Getter
81      @UriParam
82      private List<HapiServerInterceptorFactory> hapiServerInterceptorFactories;
83  
84      /**
85       * If this is true, all paging requests are routed into the route (see {@link org.openehealth.ipf.commons.ihe.fhir.LazyBundleProvider}
86       * fo details. Otherwise, all results are fetched once and cached in order to serve subsequent paging requests.
87       */
88      @Getter
89      @UriParam
90      private boolean lazyLoadBundles;
91  
92      @Getter
93      private FhirSecurityInformation securityInformation;
94  
95  
96      /**
97       * Only considered if {@link #lazyLoadBundles} is true. The (partial) results of paging requests are cached so that subsequent
98       * requests only fetch resources that have not yet been requested.
99       */
100     @Getter
101     @UriParam
102     private boolean cacheBundles = true;
103 
104     protected FhirEndpointConfiguration(FhirComponent<AuditDatasetType> component, String path, Map<String, Object> parameters) throws Exception {
105         super(component, parameters);
106         this.path = path;
107         this.context = component.initializeFhirContext();
108 
109         servletName = component.getAndRemoveParameter(parameters, "servletName", String.class, IpfFhirServlet.DEFAULT_SERVLET_NAME);
110         resourceProvider = getAndRemoveOrResolveReferenceParameters(component,
111                 parameters, "resourceProvider", AbstractPlainProvider.class);
112         clientRequestFactory = component.getAndRemoveOrResolveReferenceParameter(
113                 parameters, "clientRequestFactory", ClientRequestFactory.class);
114         hapiClientInterceptorFactories = component.getAndRemoveOrResolveReferenceParameter(
115                 parameters, "hapiClientInterceptorFactories", List.class);
116         // TODO: make use of use hapiServerInterceptorFactories
117         hapiServerInterceptorFactories = component.getAndRemoveOrResolveReferenceParameter(
118                 parameters, "hapiServerInterceptorFactories", List.class);
119 
120 
121         String parserErrorHandling = component.getAndRemoveParameter(parameters, "validation", String.class, "lenient");
122         if (STRICT.equals(parserErrorHandling)) {
123             context.setParserErrorHandler(new StrictErrorHandler());
124         } else if (!LENIENT.equals(parserErrorHandling)) {
125             throw new IllegalArgumentException("Validation must be either " + LENIENT + " (default) or " + STRICT);
126         }
127         Integer connectTimeout = component.getAndRemoveParameter(parameters, "connectionTimeout", Integer.class);
128         if (connectTimeout != null) {
129             setConnectTimeout(connectTimeout);
130         }
131         Integer timeout = component.getAndRemoveParameter(parameters, "timeout", Integer.class);
132         if (timeout != null) {
133             setTimeout(timeout);
134         }
135 
136         setDisableServerValidation(component.getAndRemoveParameter(parameters, "disableServerValidation", Boolean.class, false));
137         lazyLoadBundles = component.getAndRemoveParameter(parameters, LAZY_LOAD_BUNDLES, Boolean.class, false);
138         cacheBundles = component.getAndRemoveParameter(parameters, CACHE_BUNDLES, Boolean.class, true);
139 
140         // Security stuff
141         SSLContextParameters sslContextParameters = component.getAndRemoveOrResolveReferenceParameter(
142                 parameters, "sslContextParameters", SSLContextParameters.class);
143         HostnameVerifier hostnameVerifier = component.getAndRemoveOrResolveReferenceParameter(
144                 parameters, "hostnameVerifier", HostnameVerifier.class);
145         boolean secure = component.getAndRemoveParameter(parameters, "secure", Boolean.class, false);
146         String username = component.getAndRemoveParameter(parameters, "username", String.class);
147         String password = component.getAndRemoveParameter(parameters, "password", String.class);
148 
149         if (sslContextParameters == null) {
150             Map<String, SSLContextParameters> sslContextParameterMap = component.getCamelContext().getRegistry().findByTypeWithName(SSLContextParameters.class);
151             if (sslContextParameterMap.size() == 1) {
152                 Map.Entry<String, SSLContextParameters> entry = sslContextParameterMap.entrySet().iterator().next();
153                 sslContextParameters = entry.getValue();
154             } else if (sslContextParameterMap.size() > 1) {
155                 throw new AmbiguousBeanException(SSLContextParameters.class);
156             }
157         }
158         this.securityInformation = new FhirSecurityInformation(
159                 secure,
160                 sslContextParameters != null ?
161                         sslContextParameters.createSSLContext(component.getCamelContext()) :
162                         null,
163                 hostnameVerifier,
164                 username,
165                 password);
166     }
167 
168     public <T> List<T> getAndRemoveOrResolveReferenceParameters(FhirComponent<AuditDatasetType> component, Map<String, Object> parameters, String key, Class<T> type) {
169         String values = component.getAndRemoveParameter(parameters, key, String.class);
170         if (values == null) {
171             return null;
172         } else {
173             return Stream.of(values.split(","))
174                     .map(value ->
175                             EndpointHelper.isReferenceParameter(value) ?
176                                     EndpointHelper.resolveReferenceParameter(component.getCamelContext(), value, type) :
177                                     component.getCamelContext().getTypeConverter().convertTo(type, value))
178                     .collect(Collectors.toList());
179         }
180     }
181 
182     public <T extends IClientExecutable<?, ?>> ClientRequestFactory<T> getClientRequestFactory() {
183         return (ClientRequestFactory<T>) clientRequestFactory;
184     }
185 
186     public void setConnectTimeout(int connectTimeout) {
187         context.getRestfulClientFactory().setConnectTimeout(connectTimeout);
188     }
189 
190     public void setTimeout(int timeout) {
191         context.getRestfulClientFactory().setSocketTimeout(timeout);
192     }
193 
194     /**
195      * By default, the client will query the server before the very first operation to download the server's conformance/metadata
196      * statement and verify that the server is appropriate for the given client.
197      * This check is only done once per server endpoint for a given FhirContext and is useful to prevent bugs or unexpected behaviour
198      * when talking to servers.
199      * <p>
200      * It may introduce unneccesary overhead however in circumstances where the client and server are known to be compatible.
201      * Setting this to true disables this check.
202      */
203     public void setDisableServerValidation(boolean disableServerValidation) {
204         context.getRestfulClientFactory().setServerValidationMode(disableServerValidation ?
205                 ServerValidationModeEnum.NEVER :
206                 ServerValidationModeEnum.ONCE);
207     }
208 }