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.commons.ihe.fhir;
18  
19  import ca.uhn.fhir.context.FhirContext;
20  import ca.uhn.fhir.context.FhirVersionEnum;
21  import ca.uhn.fhir.narrative.INarrativeGenerator;
22  import ca.uhn.fhir.parser.LenientErrorHandler;
23  import ca.uhn.fhir.parser.StrictErrorHandler;
24  import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
25  import ca.uhn.fhir.rest.server.IPagingProvider;
26  import ca.uhn.fhir.rest.server.RestfulServer;
27  import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
28  import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
29  import lombok.Getter;
30  import lombok.Setter;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import javax.servlet.ServletConfig;
35  import javax.servlet.ServletException;
36  
37  /**
38   * HAPI FHIR {@link RestfulServer} implementation, adding a few configuration bits using servlet
39   * init parameters:
40   * <ul>
41   * <li>logging (boolean): add global logging interceptor</li>
42   * <li>highlight (boolean): add response formatting if request was issued from a browser</li>
43   * <li>pretty (boolean): pretty-print the response</li>
44   * <li>pagingProviderSize (integer): maximum number of concurrent paging requests</li>
45   * <li>strict (boolean): strict parsing, i.e. return error on invalid resources</li>
46   * </ul>
47   *
48   * @author Christian Ohr
49   * @since 3.2
50   */
51  public class IpfFhirServlet extends RestfulServer {
52  
53      private static final Logger LOG = LoggerFactory.getLogger(IpfFhirServlet.class);
54  
55      private static final String SERVLET_FHIR_VERSION_PARAMETER_NAME = "fhirVersion";
56      private static final String SERVLET_LOGGING_PARAMETER_NAME = "logging";
57      private static final String SERVLET_RESPONSE_HIGHLIGHTING_PARAMETER_NAME = "highlight";
58      private static final String SERVLET_PRETTY_PRINT_PARAMETER_NAME = "pretty";
59      private static final String SERVLET_PAGING_PROVIDER_SIZE_PARAMETER_NAME = "pagingProviderSize";
60      private static final String SERVLET_DEFAULT_PAGE_SIZE_PARAMETER_NAME = "defaultPageSize";
61      private static final String SERVLET_MAX_PAGE_SIZE_PARAMETER_NAME = "maximumPageSize";
62      private static final String SERVLET_STRICT_PARSER_ERROR_HANDLER_PARAMETER_NAME = "strict";
63  
64      public static final String DEFAULT_SERVLET_NAME = "FhirServlet";
65  
66  
67      @Getter @Setter
68      private String servletName = DEFAULT_SERVLET_NAME;
69      @Getter @Setter
70      private boolean logging;
71      @Getter @Setter
72      private FhirVersionEnum fhirVersion;
73      @Getter @Setter
74      private boolean responseHighlighting;
75      @Getter @Setter
76      private boolean prettyPrint;
77      @Getter @Setter
78      private int pagingProviderSize = 50;
79      @Getter @Setter
80      private int defaultPageSize = 25;
81      @Getter @Setter
82      private int maximumPageSize = 100;
83      @Getter @Setter
84      private boolean strictErrorHandler;
85      @Getter @Setter
86      private INarrativeGenerator narrativeGenerator = null;
87  
88      public IpfFhirServlet() {
89          super();
90      }
91  
92      public IpfFhirServlet(FhirVersionEnum fhirVersion) {
93          super();
94          this.fhirVersion = fhirVersion;
95      }
96  
97      /**
98       * RestfulServer assumes that all resource providers are known at init time, which is not the case here.
99       *
100      * @param config servlet config
101      * @throws ServletException
102      */
103     @Override
104     public void init(ServletConfig config) throws ServletException {
105         this.servletName = config.getServletName();
106 
107         FhirRegistry fhirRegistry = DefaultFhirRegistry.getFhirRegistry(servletName);
108         if (fhirRegistry.hasServlet(servletName)) {
109             throw new ServletException(String.format("Duplicate FHIR Servlet name %s. Use unique names per Camel application", servletName));
110         }
111 
112         try {
113             fhirRegistry.register(this);
114         } catch (Exception e) {
115             throw new ServletException(e);
116         }
117 
118         LOG.debug("Initializing IpfFhirServlet " + servletName);
119 
120         if (config.getInitParameter(SERVLET_FHIR_VERSION_PARAMETER_NAME) != null) {
121             fhirVersion = FhirVersionEnum.valueOf(config.getInitParameter(SERVLET_FHIR_VERSION_PARAMETER_NAME));
122         }
123         if (config.getInitParameter(SERVLET_LOGGING_PARAMETER_NAME) != null) {
124             logging = Boolean.parseBoolean(config.getInitParameter(SERVLET_LOGGING_PARAMETER_NAME));
125         }
126         if (config.getInitParameter(SERVLET_RESPONSE_HIGHLIGHTING_PARAMETER_NAME) != null) {
127             responseHighlighting = Boolean.parseBoolean(config.getInitParameter(SERVLET_RESPONSE_HIGHLIGHTING_PARAMETER_NAME));
128         }
129         if (config.getInitParameter(SERVLET_PRETTY_PRINT_PARAMETER_NAME) != null) {
130             prettyPrint = Boolean.parseBoolean(config.getInitParameter(SERVLET_PRETTY_PRINT_PARAMETER_NAME));
131         }
132         if (config.getInitParameter(SERVLET_STRICT_PARSER_ERROR_HANDLER_PARAMETER_NAME) != null) {
133             strictErrorHandler = Boolean.parseBoolean(config.getInitParameter(SERVLET_STRICT_PARSER_ERROR_HANDLER_PARAMETER_NAME));
134         }
135         String pagingProviderSizeParameter = config.getInitParameter(SERVLET_PAGING_PROVIDER_SIZE_PARAMETER_NAME);
136         if (pagingProviderSizeParameter != null && !pagingProviderSizeParameter.isEmpty()) {
137             pagingProviderSize = Integer.parseInt(pagingProviderSizeParameter);
138         }
139         String defaultPageSizeParameter = config.getInitParameter(SERVLET_DEFAULT_PAGE_SIZE_PARAMETER_NAME);
140         if (defaultPageSizeParameter != null && !defaultPageSizeParameter.isEmpty()) {
141             defaultPageSize = Integer.parseInt(defaultPageSizeParameter);
142         }
143         String maximumPageSizeParameter = config.getInitParameter(SERVLET_MAX_PAGE_SIZE_PARAMETER_NAME);
144         if (maximumPageSizeParameter != null && !maximumPageSizeParameter.isEmpty()) {
145             maximumPageSize = Integer.parseInt(maximumPageSizeParameter);
146         }
147 
148         // This must happen after setting all the attributes above, because it calls initialize() that
149         // access these attributes.
150         super.init(config);
151 
152     }
153 
154     @Override
155     public void destroy() {
156         FhirRegistry registry = DefaultFhirRegistry.removeFhirRegistry(getServletName());
157         if (registry != null) {
158             try {
159                 registry.unregister(this);
160             } catch (Exception e) {
161                 LOG.warn("Problem while unregistering servlet {}", getServletName(), e);
162             }
163         }
164         super.destroy();
165         LOG.info("Destroyed IpfFhirServlet [{}]", getServletName());
166     }
167 
168     /**
169      * Returns the instance of {@link IPagingProvider} to be used. This implemention returns {@link FifoMemoryPagingProvider},
170      * you may overwrite this e.g. to add a provider backed by a decent Cache implementation. In this case, not forget to set the
171      * paging parameters accessible via {@link #getPagingProviderSize()}, {@link #getDefaultPageSize()} and {@link #getMaximumPageSize()}.
172      * <p>
173      * You can also return null in order to disable paging.
174      * </p>
175      * <p>
176      * The way how paging is actually implemented depends on the respective FHIR consumer endpoints
177      * </p>
178      *
179      * @param pagingProviderSize maximum number of parallel paged requests. Note that each request may have an
180      *                           aribitrary number of result resources though.
181      * @return implementation of {@link IPagingProvider}
182      * @see IPagingProvider
183      * @see #getPagingProviderSize()
184      * @see #getMaximumPageSize()
185      * @see #getDefaultPageSize()
186      */
187     protected IPagingProvider getDefaultPagingProvider(int pagingProviderSize) {
188         FifoMemoryPagingProvider pagingProvider = new FifoMemoryPagingProvider(pagingProviderSize);
189         pagingProvider.setDefaultPageSize(getDefaultPageSize());
190         pagingProvider.setMaximumPageSize(getMaximumPageSize());
191         return pagingProvider;
192     }
193 
194     /**
195      * @return the narrative generator, null by default
196      */
197     protected INarrativeGenerator getDefaultNarrativeGenerator() {
198         return narrativeGenerator;
199     }
200 
201     /**
202      * Called upon initialization of the servlet, which is too early to know about the existing FHIR consumers
203      * initialization of Camel routes and endpoints.
204      *
205      * @throws ServletException
206      */
207     @Override
208     protected void initialize() throws ServletException {
209         setFhirContext(new FhirContext(fhirVersion));
210 
211         // Provide some more details about the request for downstream processing
212         registerInterceptor(new RequestDetailProvider());
213 
214         if (logging) {
215             LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
216             registerInterceptor(loggingInterceptor);
217             loggingInterceptor.setLoggerName(IpfFhirServlet.class.getName());
218 
219             // This is the format for each line. A number of substitution variables may
220             // be used here. See the JavaDoc for LoggingInterceptor for information on
221             // what is available.
222             loggingInterceptor.setMessageFormat("Source[${remoteAddr}] Operation[${operationType} ${idOrResourceName}] User-Agent[${requestHeader.user-agent}] Params[${requestParameters}]");
223         }
224 
225         if (responseHighlighting) {
226             ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor();
227             registerInterceptor(interceptor);
228         }
229 
230         getFhirContext().setParserErrorHandler(strictErrorHandler ? new StrictErrorHandler() : new LenientErrorHandler());
231 
232         setPagingProvider(getDefaultPagingProvider(pagingProviderSize));
233         setDefaultPrettyPrint(prettyPrint);
234 
235         /*
236          * Use a narrative generator. This is a completely optional step,
237 		 * but can be useful as it causes HAPI to generate narratives for
238 		 * resources which don't otherwise have one.
239 		 */
240         getFhirContext().setNarrativeGenerator(getDefaultNarrativeGenerator());
241 
242     }
243 
244 
245 }