001package ca.uhn.fhir.rest.server;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 * http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022import static org.apache.commons.lang3.StringUtils.isBlank;
023import static org.apache.commons.lang3.StringUtils.isNotBlank;
024
025import java.io.*;
026import java.lang.annotation.Annotation;
027import java.lang.reflect.*;
028import java.util.*;
029import java.util.Map.Entry;
030import java.util.concurrent.locks.Lock;
031import java.util.concurrent.locks.ReentrantLock;
032import java.util.jar.Manifest;
033
034import javax.servlet.ServletException;
035import javax.servlet.UnavailableException;
036import javax.servlet.http.*;
037
038import org.apache.commons.io.IOUtils;
039import org.apache.commons.lang3.StringUtils;
040import org.apache.commons.lang3.Validate;
041import org.hl7.fhir.instance.model.api.IBaseResource;
042import org.hl7.fhir.instance.model.api.IIdType;
043
044import ca.uhn.fhir.context.*;
045import ca.uhn.fhir.parser.IParser;
046import ca.uhn.fhir.rest.annotation.*;
047import ca.uhn.fhir.rest.api.MethodOutcome;
048import ca.uhn.fhir.rest.api.RequestTypeEnum;
049import ca.uhn.fhir.rest.method.*;
050import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
051import ca.uhn.fhir.rest.server.exceptions.*;
052import ca.uhn.fhir.rest.server.interceptor.*;
053import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
054import ca.uhn.fhir.util.*;
055
056public class RestfulServer extends HttpServlet implements IRestfulServer<ServletRequestDetails> {
057
058        /**
059         * All incoming requests will have an attribute added to {@link HttpServletRequest#getAttribute(String)}
060         * with this key. The value will be a Java {@link Date} with the time that request processing began.
061         */
062        public static final String REQUEST_START_TIME = RestfulServer.class.getName() + "REQUEST_START_TIME";
063
064        /**
065         * Default setting for {@link #setETagSupport(ETagSupportEnum) ETag Support}: {@link ETagSupportEnum#ENABLED}
066         */
067        public static final ETagSupportEnum DEFAULT_ETAG_SUPPORT = ETagSupportEnum.ENABLED;
068
069        private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
070
071        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
072
073        private static final long serialVersionUID = 1L;
074        /**
075         * Requests will have an HttpServletRequest attribute set with this name, containing the servlet
076         * context, in order to avoid a dependency on Servlet-API 3.0+
077         */
078        public static final String SERVLET_CONTEXT_ATTRIBUTE = "ca.uhn.fhir.rest.server.RestfulServer.servlet_context";
079        private BundleInclusionRule myBundleInclusionRule = BundleInclusionRule.BASED_ON_INCLUDES;
080        private boolean myDefaultPrettyPrint = false;
081        private EncodingEnum myDefaultResponseEncoding = EncodingEnum.XML;
082        private ETagSupportEnum myETagSupport = DEFAULT_ETAG_SUPPORT;
083        private FhirContext myFhirContext;
084        private boolean myIgnoreServerParsedRequestParameters = true;
085        private String myImplementationDescription;
086        private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
087        private IPagingProvider myPagingProvider;
088        private final List<Object> myPlainProviders = new ArrayList<Object>();
089        private Lock myProviderRegistrationMutex = new ReentrantLock();
090        private Map<String, ResourceBinding> myResourceNameToBinding = new HashMap<String, ResourceBinding>();
091        private final List<IResourceProvider> myResourceProviders = new ArrayList<IResourceProvider>();
092        private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
093        private ResourceBinding myServerBinding = new ResourceBinding();
094        private BaseMethodBinding<?> myServerConformanceMethod;
095        private Object myServerConformanceProvider;
096        private String myServerName = "HAPI FHIR Server";
097        /** This is configurable but by default we just use HAPI version */
098        private String myServerVersion = VersionUtil.getVersion();
099        private boolean myStarted;
100        private Map<String, IResourceProvider> myTypeToProvider = new HashMap<String, IResourceProvider>();
101        private boolean myUncompressIncomingContents = true;
102        private boolean myUseBrowserFriendlyContentTypes;
103
104        /**
105         * Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
106         * through {@link #setFhirContext(FhirContext)}) the server will determine which
107         * version of FHIR to support through classpath scanning. This is brittle, and it is highly recommended to explicitly
108         * specify a FHIR version.
109         */
110        public RestfulServer() {
111                this(null);
112        }
113
114        /**
115         * Constructor
116         */
117        public RestfulServer(FhirContext theCtx) {
118                myFhirContext = theCtx;
119        }
120
121        private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response, String resourceName) {
122                if (response != null && response.getId() != null) {
123                        addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION, resourceName);
124                        addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_CONTENT_LOCATION, resourceName);
125                }
126        }
127
128        /**
129         * This method is called prior to sending a response to incoming requests. It is used to add custom headers.
130         * <p>
131         * Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
132         * inadvertantly disabling functionality.
133         * </p>
134         */
135        public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
136                StringBuilder b = new StringBuilder();
137                b.append("HAPI FHIR ");
138                b.append(VersionUtil.getVersion());
139                b.append(" REST Server (FHIR Server; FHIR ");
140                b.append(myFhirContext.getVersion().getVersion().getFhirVersionString());
141                b.append('/');
142                b.append(myFhirContext.getVersion().getVersion().name());
143                b.append(")");
144                theHttpResponse.addHeader("X-Powered-By", b.toString());
145        }
146
147        private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) {
148                StringBuilder b = new StringBuilder();
149                b.append(theRequest.getFhirServerBase());
150                b.append('/');
151                b.append(resourceName);
152                b.append('/');
153                b.append(response.getId().getIdPart());
154                if (response.getId().hasVersionIdPart()) {
155                        b.append("/" + Constants.PARAM_HISTORY + "/");
156                        b.append(response.getId().getVersionIdPart());
157                }
158                theResponse.addHeader(headerLocation, b.toString());
159
160        }
161
162        private void assertProviderIsValid(Object theNext) throws ConfigurationException {
163                if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
164                        throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class must be public");
165                }
166        }
167
168        public RestulfulServerConfiguration createConfiguration() {
169                RestulfulServerConfiguration result = new RestulfulServerConfiguration();
170                result.setResourceBindings(getResourceBindings());
171                result.setServerBindings(getServerBindings());
172                result.setImplementationDescription(getImplementationDescription());
173                result.setServerVersion(getServerVersion());
174                result.setServerName(getServerName());
175                result.setFhirContext(getFhirContext());
176                result.setServerAddressStrategy(myServerAddressStrategy);
177                InputStream inputStream = null;
178                try {
179                        inputStream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF");
180                        if (inputStream != null) {
181                                Manifest manifest = new Manifest(inputStream);
182                                result.setConformanceDate(manifest.getMainAttributes().getValue("Build-Time"));
183                        }
184                } catch (IOException e) {
185                        // fall through
186                } finally {
187                        if (inputStream != null) {
188                                IOUtils.closeQuietly(inputStream);
189                        }
190                }
191                return result;
192        }
193
194        @Override
195        public void destroy() {
196                if (getResourceProviders() != null) {
197                        for (IResourceProvider iResourceProvider : getResourceProviders()) {
198                                invokeDestroy(iResourceProvider);
199                        }
200                }
201                if (myServerConformanceProvider != null) {
202                        invokeDestroy(myServerConformanceProvider);
203                }
204                if (getPlainProviders() != null) {
205                        for (Object next : getPlainProviders()) {
206                                invokeDestroy(next);
207                        }
208                }
209        }
210
211        public BaseMethodBinding<?> determineResourceMethod(RequestDetails requestDetails, String requestPath) {
212                RequestTypeEnum requestType = requestDetails.getRequestType();
213
214                ResourceBinding resourceBinding = null;
215                BaseMethodBinding<?> resourceMethod = null;
216                String resourceName = requestDetails.getResourceName();
217                if (myServerConformanceMethod.incomingServerRequestMatchesMethod(requestDetails)) {
218                        resourceMethod = myServerConformanceMethod;
219                } else if (resourceName == null) {
220                        resourceBinding = myServerBinding;
221                } else {
222                        resourceBinding = myResourceNameToBinding.get(resourceName);
223                        if (resourceBinding == null) {
224                                throw new ResourceNotFoundException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToBinding.keySet());
225                        }
226                }
227
228                if (resourceMethod == null) {
229                        if (resourceBinding != null) {
230                                resourceMethod = resourceBinding.getMethod(requestDetails);
231                        }
232                }
233                if (resourceMethod == null) {
234                        if (isBlank(requestPath)) {
235                                throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "rootRequest"));
236                        } 
237                        throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", requestType.name(), requestPath, requestDetails.getParameters().keySet()));
238                }
239                return resourceMethod;
240        }
241
242        /**
243         * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
244         */
245        protected int escapedLength(String theServletPath) {
246                int delta = 0;
247                for (int i = 0; i < theServletPath.length(); i++) {
248                        char next = theServletPath.charAt(i);
249                        if (next == ' ') {
250                                delta = delta + 2;
251                        }
252                }
253                return theServletPath.length() + delta;
254        }
255
256        private void findResourceMethods(Object theProvider) {
257
258                ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
259                int count = 0;
260
261                Class<?> clazz = theProvider.getClass();
262                Class<?> supertype = clazz.getSuperclass();
263                while (!Object.class.equals(supertype)) {
264                        count += findResourceMethods(theProvider, supertype);
265                        supertype = supertype.getSuperclass();
266                }
267
268                count += findResourceMethods(theProvider, clazz);
269
270                if (count == 0) {
271                        throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName());
272                }
273        }
274
275        private int findResourceMethods(Object theProvider, Class<?> clazz) throws ConfigurationException {
276                int count = 0;
277
278                for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
279                        BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider);
280                        if (foundMethodBinding == null) {
281                                continue;
282                        }
283
284                        count++;
285
286                        if (foundMethodBinding instanceof ConformanceMethodBinding) {
287                                myServerConformanceMethod = foundMethodBinding;
288                                continue;
289                        }
290
291                        if (!Modifier.isPublic(m.getModifiers())) {
292                                throw new ConfigurationException("Method '" + m.getName() + "' is not public, FHIR RESTful methods must be public");
293                        }
294                        if (Modifier.isStatic(m.getModifiers())) {
295                                throw new ConfigurationException("Method '" + m.getName() + "' is static, FHIR RESTful methods must not be static");
296                        } 
297                        ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
298
299                        String resourceName = foundMethodBinding.getResourceName();
300                        ResourceBinding resourceBinding;
301                        if (resourceName == null) {
302                                resourceBinding = myServerBinding;
303                        } else {
304                                RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName);
305                                if (myResourceNameToBinding.containsKey(definition.getName())) {
306                                        resourceBinding = myResourceNameToBinding.get(definition.getName());
307                                } else {
308                                        resourceBinding = new ResourceBinding();
309                                        resourceBinding.setResourceName(resourceName);
310                                        myResourceNameToBinding.put(resourceName, resourceBinding);
311                                }
312                        }
313
314                        List<Class<?>> allowableParams = foundMethodBinding.getAllowableParamAnnotations();
315                        if (allowableParams != null) {
316                                for (Annotation[] nextParamAnnotations : m.getParameterAnnotations()) {
317                                        for (Annotation annotation : nextParamAnnotations) {
318                                                Package pack = annotation.annotationType().getPackage();
319                                                if (pack.equals(IdParam.class.getPackage())) {
320                                                        if (!allowableParams.contains(annotation.annotationType())) {
321                                                                throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation);
322                                                        }
323                                                }
324                                        }
325                                }
326                        }
327
328                        resourceBinding.addMethod(foundMethodBinding);
329                        ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
330
331                }
332
333                return count;
334        }
335
336        /**
337         * @deprecated As of HAPI FHIR 1.5, this property has been moved to
338         *             {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
339         */
340        @Override
341        @Deprecated
342        public AddProfileTagEnum getAddProfileTag() {
343                return myFhirContext.getAddProfileTagWhenEncoding();
344        }
345
346        @Override
347        public BundleInclusionRule getBundleInclusionRule() {
348                return myBundleInclusionRule;
349        }
350
351        /**
352         * Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either
353         * with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
354         * in the request. The default is {@link EncodingEnum#XML}. Will not return null.
355         */
356        @Override
357        public EncodingEnum getDefaultResponseEncoding() {
358                return myDefaultResponseEncoding;
359        }
360
361        @Override
362        public ETagSupportEnum getETagSupport() {
363                return myETagSupport;
364        }
365
366        /**
367         * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
368         * providers should generally use this context if one is needed, as opposed to
369         * creating their own.
370         */
371        @Override
372        public FhirContext getFhirContext() {
373                if (myFhirContext == null) {
374                        //TODO: Use of a deprecated method should be resolved.
375                        myFhirContext = new FhirContext();
376                }
377                return myFhirContext;
378        }
379
380        public String getImplementationDescription() {
381                return myImplementationDescription;
382        }
383
384        /**
385         * Returns a list of all registered server interceptors
386         */
387        @Override
388        public List<IServerInterceptor> getInterceptors() {
389                return Collections.unmodifiableList(myInterceptors);
390        }
391
392        @Override
393        public IPagingProvider getPagingProvider() {
394                return myPagingProvider;
395        }
396
397        /**
398         * Provides the non-resource specific providers which implement method calls on this server
399         *
400         * @see #getResourceProviders()
401         */
402        public Collection<Object> getPlainProviders() {
403                return myPlainProviders;
404        }
405
406        /**
407         * Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
408         * implementation
409         *
410         * @param requestFullPath
411         *           the full request path
412         * @param servletContextPath
413         *           the servelet context path
414         * @param servletPath
415         *           the servelet path
416         * @return created resource path
417         */
418        protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
419                return requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath));
420        }
421
422        public Collection<ResourceBinding> getResourceBindings() {
423                return myResourceNameToBinding.values();
424        }
425
426        /**
427         * Provides the resource providers for this server
428         */
429        public Collection<IResourceProvider> getResourceProviders() {
430                return myResourceProviders;
431        }
432
433        /**
434         * Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
435         * server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
436         */
437        public IServerAddressStrategy getServerAddressStrategy() {
438                return myServerAddressStrategy;
439        }
440
441        /**
442         * Returns the server base URL (with no trailing '/') for a given request
443         */
444        public String getServerBaseForRequest(HttpServletRequest theRequest) {
445                String fhirServerBase;
446                fhirServerBase = myServerAddressStrategy.determineServerBase(getServletContext(), theRequest);
447
448                if (fhirServerBase.endsWith("/")) {
449                        fhirServerBase = fhirServerBase.substring(0, fhirServerBase.length() - 1);
450                }
451                return fhirServerBase;
452        }
453
454        /**
455         * Returns the method bindings for this server which are not specific to any particular resource type. This method is
456         * internal to HAPI and developers generally do not need to interact with it. Use
457         * with caution, as it may change.
458         */
459        public List<BaseMethodBinding<?>> getServerBindings() {
460                return myServerBinding.getMethodBindings();
461        }
462
463        /**
464         * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
465         * (metadata) statement if one has been explicitly defined.
466         * <p>
467         * By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
468         * set to <code>null</code> to use the appropriate one for the given FHIR version.
469         * </p>
470         */
471        public Object getServerConformanceProvider() {
472                return myServerConformanceProvider;
473        }
474
475        /**
476         * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
477         * but can be helpful to set with something appropriate.
478         *
479         * @see RestfulServer#setServerName(String)
480         */
481        public String getServerName() {
482                return myServerName;
483        }
484
485        public IResourceProvider getServerProfilesProvider() {
486                return getFhirContext().getVersion().createServerProfilesProvider(this);
487        }
488
489        /**
490         * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
491         * but can be helpful to set with something appropriate.
492         */
493        public String getServerVersion() {
494                return myServerVersion;
495        }
496
497        protected void handleRequest(RequestTypeEnum theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
498                String fhirServerBase = null;
499                ServletRequestDetails requestDetails = new ServletRequestDetails();
500                requestDetails.setServer(this);
501                requestDetails.setRequestType(theRequestType);
502                requestDetails.setServletRequest(theRequest);
503                requestDetails.setServletResponse(theResponse);
504
505                theRequest.setAttribute(SERVLET_CONTEXT_ATTRIBUTE, getServletContext());
506
507                try {
508
509                        /* ***********************************
510                         * Parse out the request parameters
511                         * ***********************************/
512                        
513                        String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
514                        String servletPath = StringUtils.defaultString(theRequest.getServletPath());
515                        StringBuffer requestUrl = theRequest.getRequestURL();
516                        String servletContextPath = IncomingRequestAddressStrategy.determineServletContextPath(theRequest, this);
517
518                        /*
519                         * Just for debugging..
520                         */
521                        if (ourLog.isTraceEnabled()) {
522                                ourLog.trace("Request FullPath: {}", requestFullPath);
523                                ourLog.trace("Servlet Path: {}", servletPath);
524                                ourLog.trace("Request Url: {}", requestUrl);
525                                ourLog.trace("Context Path: {}", servletContextPath);
526                        }
527
528                        String completeUrl;
529                        Map<String, String[]> params = null;
530                        if (StringUtils.isNotBlank(theRequest.getQueryString())) {
531                                completeUrl = requestUrl + "?" + theRequest.getQueryString();
532                                /*
533                                 * By default, we manually parse the request params (the URL params, or the body for
534                                 * POST form queries) since Java containers can't be trusted to use UTF-8 encoding
535                                 * when parsing. Specifically Tomcat 7 and Glassfish 4.0 use 8859-1 for some dumb
536                                 * reason.... grr.....
537                                 */
538                                if (isIgnoreServerParsedRequestParameters()) {
539                                        String contentType = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
540                                        if (theRequestType == RequestTypeEnum.POST && isNotBlank(contentType) && contentType.startsWith(Constants.CT_X_FORM_URLENCODED)) {
541                                                String requestBody = new String(requestDetails.loadRequestContents(), Constants.CHARSET_UTF8);
542                                                params = UrlUtil.parseQueryStrings(theRequest.getQueryString(), requestBody);
543                                        } else if (theRequestType == RequestTypeEnum.GET) {
544                                                params = UrlUtil.parseQueryString(theRequest.getQueryString());
545                                        }
546                                }
547                        } else {
548                                completeUrl = requestUrl.toString();
549                        }
550
551                        if (params == null) {
552                                params = new HashMap<String, String[]>(theRequest.getParameterMap());
553                        }
554
555                        requestDetails.setParameters(params);
556
557                        /* *************************
558                         * Notify interceptors about the incoming request
559                         * *************************/
560                        
561                        for (IServerInterceptor next : myInterceptors) {
562                                boolean continueProcessing = next.incomingRequestPreProcessed(theRequest, theResponse);
563                                if (!continueProcessing) {
564                                        ourLog.debug("Interceptor {} returned false, not continuing processing");
565                                        return;
566                                }
567                        }
568                        
569                        
570                        String requestPath = getRequestPath(requestFullPath, servletContextPath, servletPath);
571
572                        if (requestPath.length() > 0 && requestPath.charAt(0) == '/') {
573                                requestPath = requestPath.substring(1);
574                        }
575
576                        fhirServerBase = getServerBaseForRequest(theRequest);
577
578                        
579                        IIdType id;
580                        populateRequestDetailsFromRequestPath(requestDetails, requestPath);
581
582                        if (theRequestType == RequestTypeEnum.PUT) {
583                                String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION);
584                                if (contentLocation != null) {
585                                        id = myFhirContext.getVersion().newIdType();
586                                        id.setValue(contentLocation);
587                                        requestDetails.setId(id);
588                                }
589                        }
590
591                        String acceptEncoding = theRequest.getHeader(Constants.HEADER_ACCEPT_ENCODING);
592                        boolean respondGzip = false;
593                        if (acceptEncoding != null) {
594                                String[] parts = acceptEncoding.trim().split("\\s*,\\s*");
595                                for (String string : parts) {
596                                        if (string.equals("gzip")) {
597                                                respondGzip = true;
598                                        }
599                                }
600                        }
601                        requestDetails.setRespondGzip(respondGzip);
602                        requestDetails.setRequestPath(requestPath);
603                        requestDetails.setFhirServerBase(fhirServerBase);
604                        requestDetails.setCompleteUrl(completeUrl);
605
606                        // String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION);
607                        // if (getPagingProvider() != null && isNotBlank(pagingAction)) {
608                        // requestDetails.setRestOperationType(RestOperationTypeEnum.GET_PAGE);
609                        // if (theRequestType != RequestTypeEnum.GET) {
610                        // /*
611                        // * We reconstruct the link-self URL using the request parameters, and this would break if the parameters came
612                        // in using a POST. We could probably work around that but why bother unless
613                        // * someone comes up with a reason for needing it.
614                        // */
615                        // throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(RestfulServer.class,
616                        // "getPagesNonHttpGet"));
617                        // }
618                        // handlePagingRequest(requestDetails, theResponse, pagingAction);
619                        // return;
620                        // }
621
622                        BaseMethodBinding<?> resourceMethod = determineResourceMethod(requestDetails, requestPath);
623
624                        requestDetails.setRestOperationType(resourceMethod.getRestOperationType());
625
626                        // Handle server interceptors
627                        for (IServerInterceptor next : myInterceptors) {
628                                boolean continueProcessing = next.incomingRequestPostProcessed(requestDetails, theRequest, theResponse);
629                                if (!continueProcessing) {
630                                        ourLog.debug("Interceptor {} returned false, not continuing processing");
631                                        return;
632                                }
633                        }
634
635                        /*
636                         * Actualy invoke the server method. This call is to a HAPI method binding, which
637                         * is an object that wraps a specific implementing (user-supplied) method, but
638                         * handles its input and provides its output back to the client.
639                         * 
640                         * This is basically the end of processing for a successful request, since the
641                         * method binding replies to the client and closes the response.
642                         */
643                        Closeable outputStreamOrWriter = (Closeable) resourceMethod.invokeServer(this, requestDetails);
644
645                        for (int i = getInterceptors().size() - 1; i >= 0; i--) {
646                                IServerInterceptor next = getInterceptors().get(i);
647                                next.processingCompletedNormally(requestDetails);
648                        }
649
650                        if (outputStreamOrWriter != null) {
651                                outputStreamOrWriter.close();
652                        }
653                        
654                } catch (NotModifiedException e) {
655
656                        for (int i = getInterceptors().size() - 1; i >= 0; i--) {
657                                IServerInterceptor next = getInterceptors().get(i);
658                                if (!next.handleException(requestDetails, e, theRequest, theResponse)) {
659                                        ourLog.debug("Interceptor {} returned false, not continuing processing");
660                                        return;
661                                }
662                        }
663                        writeExceptionToResponse(theResponse, e);
664
665                } catch (AuthenticationException e) {
666
667                        for (int i = getInterceptors().size() - 1; i >= 0; i--) {
668                                IServerInterceptor next = getInterceptors().get(i);
669                                if (!next.handleException(requestDetails, e, theRequest, theResponse)) {
670                                        ourLog.debug("Interceptor {} returned false, not continuing processing");
671                                        return;
672                                }
673                        }
674
675                        writeExceptionToResponse(theResponse, e);
676
677                } catch (Throwable e) {
678
679                        /*
680                         * We have caught an exception during request processing. This might be because a handling method threw
681                         * something they wanted to throw (e.g. UnprocessableEntityException because the request
682                         * had business requirement problems) or it could be due to bugs (e.g. NullPointerException).
683                         * 
684                         * First we let the interceptors have a crack at converting the exception into something HAPI can use
685                         * (BaseServerResponseException)
686                         */
687                        BaseServerResponseException exception = null;
688                        for (int i = getInterceptors().size() - 1; i >= 0; i--) {
689                                IServerInterceptor next = getInterceptors().get(i);
690                                exception = next.preProcessOutgoingException(requestDetails, e, theRequest);
691                                if (exception != null) {
692                                        ourLog.debug("Interceptor {} returned false, not continuing processing");
693                                        break;
694                                }
695                        }
696
697                        /*
698                         * If none of the interceptors converted the exception, default behaviour is to keep the exception as-is if it
699                         * extends BaseServerResponseException, otherwise wrap it in an
700                         * InternalErrorException.
701                         */
702                        if (exception == null) {
703                                exception = DEFAULT_EXCEPTION_HANDLER.preProcessOutgoingException(requestDetails, e, theRequest);
704                        }
705
706                        /*
707                         * Next, interceptors get a shot at handling the exception
708                         */
709                        for (int i = getInterceptors().size() - 1; i >= 0; i--) {
710                                IServerInterceptor next = getInterceptors().get(i);
711                                if (!next.handleException(requestDetails, exception, theRequest, theResponse)) {
712                                        ourLog.debug("Interceptor {} returned false, not continuing processing");
713                                        return;
714                                }
715                        }
716
717                        /*
718                         * If we're handling an exception, no summary mode should be applied
719                         */
720                        requestDetails.getParameters().remove(Constants.PARAM_SUMMARY);
721                        requestDetails.getParameters().remove(Constants.PARAM_ELEMENTS);
722
723                        /*
724                         * If nobody handles it, default behaviour is to stream back the OperationOutcome to the client.
725                         */
726                        DEFAULT_EXCEPTION_HANDLER.handleException(requestDetails, exception, theRequest, theResponse);
727
728                }
729        }
730
731        /**
732         * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
733         * but subclasses may put initialization code in {@link #initialize()}, which is
734         * called immediately before beginning initialization of the restful server's internal init.
735         */
736        @Override
737        public final void init() throws ServletException {
738                myProviderRegistrationMutex.lock();
739                try {
740                        initialize();
741
742                        Object confProvider;
743                        try {
744                                ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode");
745
746                                ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
747                                providedResourceScanner.scanForProvidedResources(this);
748
749                                Collection<IResourceProvider> resourceProvider = getResourceProviders();
750                                // 'true' tells registerProviders() that
751                                // this call is part of initialization
752                                registerProviders(resourceProvider, true);
753
754                                Collection<Object> providers = getPlainProviders();
755                                // 'true' tells registerProviders() that
756                                // this call is part of initialization
757                                registerProviders(providers, true);
758
759                                findResourceMethods(getServerProfilesProvider());
760
761                                confProvider = getServerConformanceProvider();
762                                if (confProvider == null) {
763                                        confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
764                                }
765                                // findSystemMethods(confProvider);
766                                findResourceMethods(confProvider);
767
768                                ourLog.trace("Invoking provider initialize methods");
769                                if (getResourceProviders() != null) {
770                                        for (IResourceProvider iResourceProvider : getResourceProviders()) {
771                                                invokeInitialize(iResourceProvider);
772                                        }
773                                }
774                                if (confProvider != null) {
775                                        invokeInitialize(confProvider);
776                                }
777                                if (getPlainProviders() != null) {
778                                        for (Object next : getPlainProviders()) {
779                                                invokeInitialize(next);
780                                        }
781                                }
782
783                                /*
784                                 * This is a bit odd, but we have a placeholder @GetPage method for now
785                                 * that gets the server to bind for the paging request. At some point
786                                 * it would be nice to set things up so that client code could provide
787                                 * an alternate implementation, but this isn't currently possible..
788                                 */
789                                findResourceMethods(new PageProvider());
790
791                        } catch (Exception ex) {
792                                ourLog.error("An error occurred while loading request handlers!", ex);
793                                throw new ServletException("Failed to initialize FHIR Restful server", ex);
794                        }
795
796                        myStarted = true;
797                        ourLog.info("A FHIR has been lit on this server");
798                } finally {
799                        myProviderRegistrationMutex.unlock();
800                }
801        }
802
803        /**
804         * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
805         * server being used.
806         * 
807         * @throws ServletException
808         *            If the initialization failed. Note that you should consider throwing {@link UnavailableException}
809         *            (which extends {@link ServletException}), as this is a flag to the servlet container
810         *            that the servlet is not usable.
811         */
812        protected void initialize() throws ServletException {
813                // nothing by default
814        }
815
816        private void invokeDestroy(Object theProvider) {
817                invokeDestroy(theProvider, theProvider.getClass());
818        }
819
820        private void invokeDestroy(Object theProvider, Class<?> clazz) {
821                for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
822                        Destroy destroy = m.getAnnotation(Destroy.class);
823                        if (destroy != null) {
824                                try {
825                                        m.invoke(theProvider);
826                                } catch (IllegalAccessException e) {
827                                        ourLog.error("Exception occurred in destroy ", e);
828                                } catch (InvocationTargetException e) {
829                                        ourLog.error("Exception occurred in destroy ", e);
830                                }
831                                return;
832                        }
833                }
834
835                Class<?> supertype = clazz.getSuperclass();
836                if (!Object.class.equals(supertype)) {
837                        invokeDestroy(theProvider, supertype);
838                }
839        }
840
841        private void invokeInitialize(Object theProvider) {
842                invokeInitialize(theProvider, theProvider.getClass());
843        }
844
845        private void invokeInitialize(Object theProvider, Class<?> clazz) {
846                for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
847                        Initialize initialize = m.getAnnotation(Initialize.class);
848                        if (initialize != null) {
849                                try {
850                                        m.invoke(theProvider);
851                                } catch (IllegalAccessException e) {
852                                        ourLog.error("Exception occurred in destroy ", e);
853                                } catch (InvocationTargetException e) {
854                                        ourLog.error("Exception occurred in destroy ", e);
855                                }
856                                return;
857                        }
858                }
859
860                Class<?> supertype = clazz.getSuperclass();
861                if (!Object.class.equals(supertype)) {
862                        invokeInitialize(theProvider, supertype);
863                }
864        }
865
866        /**
867         * Should the server "pretty print" responses by default (requesting clients can always override this default by
868         * supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
869         * parameter in the request URL.
870         * <p>
871         * The default is <code>false</code>
872         * </p>
873         * 
874         * @return Returns the default pretty print setting
875         */
876        @Override
877        public boolean isDefaultPrettyPrint() {
878                return myDefaultPrettyPrint;
879        }
880
881        /**
882         * If set to <code>true</code> (the default is <code>true</code>) this server will not
883         * use the parsed request parameters (URL parameters and HTTP POST form contents) but
884         * will instead parse these values manually from the request URL and request body.
885         * <p>
886         * This is useful because many servlet containers (e.g. Tomcat, Glassfish) will use
887         * ISO-8859-1 encoding to parse escaped URL characters instead of using UTF-8
888         * as is specified by FHIR.
889         * </p>
890         */
891        public boolean isIgnoreServerParsedRequestParameters() {
892                return myIgnoreServerParsedRequestParameters;
893        }
894
895        /**
896         * Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this
897         * should be set to <code>true</code> unless the server has other configuration to
898         * deal with decompressing request bodies (e.g. a filter applied to the whole server).
899         */
900        public boolean isUncompressIncomingContents() {
901                return myUncompressIncomingContents;
902        }
903
904        /**
905         * @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
906         *             instead as an interceptor on your server and it will provide more useful syntax
907         *             highlighting. Deprocated in 1.4
908         */
909        @Deprecated
910        @Override
911        public boolean isUseBrowserFriendlyContentTypes() {
912                return myUseBrowserFriendlyContentTypes;
913        }
914
915        public void populateRequestDetailsFromRequestPath(RequestDetails theRequestDetails, String theRequestPath) {
916                StringTokenizer tok = new UrlPathTokenizer(theRequestPath);
917                String resourceName = null;
918
919                IIdType id = null;
920                String operation = null;
921                String compartment = null;
922                if (tok.hasMoreTokens()) {
923                        resourceName = tok.nextToken();
924                        if (partIsOperation(resourceName)) {
925                                operation = resourceName;
926                                resourceName = null;
927                        }
928                }
929                theRequestDetails.setResourceName(resourceName);
930
931                if (tok.hasMoreTokens()) {
932                        String nextString = tok.nextToken();
933                        if (partIsOperation(nextString)) {
934                                operation = nextString;
935                        } else {
936                                id = myFhirContext.getVersion().newIdType();
937                                id.setParts(null, resourceName, UrlUtil.unescape(nextString), null);
938                        }
939                }
940
941                if (tok.hasMoreTokens()) {
942                        String nextString = tok.nextToken();
943                        if (nextString.equals(Constants.PARAM_HISTORY)) {
944                                if (tok.hasMoreTokens()) {
945                                        String versionString = tok.nextToken();
946                                        if (id == null) {
947                                                throw new InvalidRequestException("Don't know how to handle request path: " + theRequestPath);
948                                        }
949                                        id.setParts(null, resourceName, id.getIdPart(), UrlUtil.unescape(versionString));
950                                } else {
951                                        operation = Constants.PARAM_HISTORY;
952                                }
953                        } else if (partIsOperation(nextString)) {
954                                if (operation != null) {
955                                        throw new InvalidRequestException("URL Path contains two operations: " + theRequestPath);
956                                }
957                                operation = nextString;
958                        } else {
959                                compartment = nextString;
960                        }
961                }
962
963                // Secondary is for things like ..../_tags/_delete
964                String secondaryOperation = null;
965
966                while (tok.hasMoreTokens()) {
967                        String nextString = tok.nextToken();
968                        if (operation == null) {
969                                operation = nextString;
970                        } else if (secondaryOperation == null) {
971                                secondaryOperation = nextString;
972                        } else {
973                                throw new InvalidRequestException("URL path has unexpected token '" + nextString + "' at the end: " + theRequestPath);
974                        }
975                }
976
977                theRequestDetails.setId(id);
978                theRequestDetails.setOperation(operation);
979                theRequestDetails.setSecondaryOperation(secondaryOperation);
980                theRequestDetails.setCompartmentName(compartment);
981        }
982
983        public void registerInterceptor(IServerInterceptor theInterceptor) {
984                Validate.notNull(theInterceptor, "Interceptor can not be null");
985                myInterceptors.add(theInterceptor);
986        }
987
988        /**
989         * Register a single provider. This could be a Resource Provider or a "plain" provider not associated with any
990         * resource.
991         */
992        public void registerProvider(Object provider) {
993                if (provider != null) {
994                        Collection<Object> providerList = new ArrayList<Object>(1);
995                        providerList.add(provider);
996                        registerProviders(providerList);
997                }
998        }
999
1000        /**
1001         * Register a group of providers. These could be Resource Providers, "plain" providers or a mixture of the two.
1002         * 
1003         * @param providers
1004         *           a {@code Collection} of providers. The parameter could be null or an empty {@code Collection}
1005         */
1006        public void registerProviders(Collection<? extends Object> providers) {
1007                myProviderRegistrationMutex.lock();
1008                try {
1009                        if (!myStarted) {
1010                                for (Object provider : providers) {
1011                                        ourLog.info("Registration of provider [" + provider.getClass().getName() + "] will be delayed until FHIR server startup");
1012                                        if (provider instanceof IResourceProvider) {
1013                                                myResourceProviders.add((IResourceProvider) provider);
1014                                        } else {
1015                                                myPlainProviders.add(provider);
1016                                        }
1017                                }
1018                                return;
1019                        }
1020                } finally {
1021                        myProviderRegistrationMutex.unlock();
1022                }
1023                registerProviders(providers, false);
1024        }
1025
1026        /*
1027         * Inner method to actually register providers
1028         */
1029        protected void registerProviders(Collection<? extends Object> providers, boolean inInit) {
1030                List<IResourceProvider> newResourceProviders = new ArrayList<IResourceProvider>();
1031                List<Object> newPlainProviders = new ArrayList<Object>();
1032                ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
1033
1034                if (providers != null) {
1035                        for (Object provider : providers) {
1036                                if (provider instanceof IResourceProvider) {
1037                                        IResourceProvider rsrcProvider = (IResourceProvider) provider;
1038                                        Class<? extends IBaseResource> resourceType = rsrcProvider.getResourceType();
1039                                        if (resourceType == null) {
1040                                                throw new NullPointerException("getResourceType() on class '" + rsrcProvider.getClass().getCanonicalName() + "' returned null");
1041                                        }
1042                                        String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
1043                                        if (myTypeToProvider.containsKey(resourceName)) {
1044                                                throw new ConfigurationException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName()
1045                                                                + "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
1046                                        }
1047                                        if (!inInit) {
1048                                                myResourceProviders.add(rsrcProvider);
1049                                        }
1050                                        myTypeToProvider.put(resourceName, rsrcProvider);
1051                                        providedResourceScanner.scanForProvidedResources(rsrcProvider);
1052                                        newResourceProviders.add(rsrcProvider);
1053                                } else {
1054                                        if (!inInit) {
1055                                                myPlainProviders.add(provider);
1056                                        }
1057                                        newPlainProviders.add(provider);
1058                                }
1059
1060                        }
1061                        if (!newResourceProviders.isEmpty()) {
1062                                ourLog.info("Added {} resource provider(s). Total {}", newResourceProviders.size(), myTypeToProvider.size());
1063                                for (IResourceProvider provider : newResourceProviders) {
1064                                        assertProviderIsValid(provider);
1065                                        findResourceMethods(provider);
1066                                }
1067                        }
1068                        if (!newPlainProviders.isEmpty()) {
1069                                ourLog.info("Added {} plain provider(s). Total {}", newPlainProviders.size());
1070                                for (Object provider : newPlainProviders) {
1071                                        assertProviderIsValid(provider);
1072                                        findResourceMethods(provider);
1073                                }
1074                        }
1075                        if (!inInit) {
1076                                ourLog.trace("Invoking provider initialize methods");
1077                                if (!newResourceProviders.isEmpty()) {
1078                                        for (IResourceProvider provider : newResourceProviders) {
1079                                                invokeInitialize(provider);
1080                                        }
1081                                }
1082                                if (!newPlainProviders.isEmpty()) {
1083                                        for (Object provider : newPlainProviders) {
1084                                                invokeInitialize(provider);
1085                                        }
1086                                }
1087                        }
1088                }
1089        }
1090
1091        /*
1092         * Remove registered RESTful methods for a Provider (and all superclasses) when it is being unregistered
1093         */
1094        private void removeResourceMethods(Object theProvider) throws Exception {
1095                ourLog.info("Removing RESTful methods for: {}", theProvider.getClass());
1096                Class<?> clazz = theProvider.getClass();
1097                Class<?> supertype = clazz.getSuperclass();
1098                Collection<String> resourceNames = new ArrayList<String>();
1099                while (!Object.class.equals(supertype)) {
1100                        removeResourceMethods(theProvider, supertype, resourceNames);
1101                        supertype = supertype.getSuperclass();
1102                }
1103                removeResourceMethods(theProvider, clazz, resourceNames);
1104                for (String resourceName : resourceNames) {
1105                        myResourceNameToBinding.remove(resourceName);
1106                }
1107        }
1108
1109        /*
1110         * Collect the set of RESTful methods for a single class when it is being unregistered
1111         */
1112        private void removeResourceMethods(Object theProvider, Class<?> clazz, Collection<String> resourceNames) throws ConfigurationException {
1113                for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
1114                        BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider);
1115                        if (foundMethodBinding == null) {
1116                                continue; // not a bound method
1117                        }
1118                        if (foundMethodBinding instanceof ConformanceMethodBinding) {
1119                                myServerConformanceMethod = null;
1120                                continue;
1121                        }
1122                        String resourceName = foundMethodBinding.getResourceName();
1123                        if (!resourceNames.contains(resourceName)) {
1124                                resourceNames.add(resourceName);
1125                        }
1126                }
1127        }
1128
1129        public Object returnResponse(ServletRequestDetails theRequest, ParseAction<?> outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException {
1130                HttpServletResponse servletResponse = theRequest.getServletResponse();
1131                servletResponse.setStatus(operationStatus);
1132                servletResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8);
1133                addHeadersToResponse(servletResponse);
1134                if (allowPrefer) {
1135                        addContentLocationHeaders(theRequest, servletResponse, response, resourceName);
1136                }
1137                Writer writer;
1138                if (outcome != null) {
1139                        ResponseEncoding encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequest);
1140                        servletResponse.setContentType(encoding.getResourceContentType());
1141                        writer = servletResponse.getWriter();
1142                        IParser parser = encoding.getEncoding().newParser(getFhirContext());
1143                        parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(this, theRequest));
1144                        outcome.execute(parser, writer);
1145                } else {
1146                        servletResponse.setContentType(Constants.CT_TEXT_WITH_UTF8);
1147                        writer = servletResponse.getWriter();
1148                }
1149                return writer;
1150        }
1151
1152        @Override
1153        protected void service(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
1154                theReq.setAttribute(REQUEST_START_TIME, new Date());
1155                
1156                RequestTypeEnum method;
1157                try {
1158                        method = RequestTypeEnum.valueOf(theReq.getMethod());
1159                } catch (IllegalArgumentException e) {
1160                        super.service(theReq, theResp);
1161                        return;
1162                }
1163
1164                switch (method) {
1165                case DELETE:
1166                        doDelete(theReq, theResp);
1167                        break;
1168                case GET:
1169                        doGet(theReq, theResp);
1170                        break;
1171                case OPTIONS:
1172                        doOptions(theReq, theResp);
1173                        break;
1174                case POST:
1175                        doPost(theReq, theResp);
1176                        break;
1177                case PUT:
1178                        doPut(theReq, theResp);
1179                        break;
1180                default:
1181                        handleRequest(method, theReq, theResp);
1182                        break;
1183                }
1184        }
1185
1186        @Override
1187        protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
1188                handleRequest(RequestTypeEnum.DELETE, request, response);
1189        }
1190
1191        @Override
1192        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
1193                handleRequest(RequestTypeEnum.GET, request, response);
1194        }
1195
1196        @Override
1197        protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
1198                handleRequest(RequestTypeEnum.OPTIONS, theReq, theResp);
1199        }
1200
1201        @Override
1202        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
1203                handleRequest(RequestTypeEnum.POST, request, response);
1204        }
1205
1206        @Override
1207        protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
1208                handleRequest(RequestTypeEnum.PUT, request, response);
1209        }
1210
1211        /**
1212         * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
1213         * (which is the default), the server will automatically add a profile tag based on
1214         * the class of the resource(s) being returned.
1215         *
1216         * @param theAddProfileTag
1217         *           The behaviour enum (must not be null)
1218         * @deprecated As of HAPI FHIR 1.5, this property has been moved to
1219         *             {@link FhirContext#setAddProfileTagWhenEncoding(AddProfileTagEnum)}
1220         */
1221        @Deprecated
1222        @CoverageIgnore
1223        public void setAddProfileTag(AddProfileTagEnum theAddProfileTag) {
1224                Validate.notNull(theAddProfileTag, "theAddProfileTag must not be null");
1225                myFhirContext.setAddProfileTagWhenEncoding(theAddProfileTag);
1226        }
1227
1228        /**
1229         * Set how bundle factory should decide whether referenced resources should be included in bundles
1230         *
1231         * @param theBundleInclusionRule
1232         *           - inclusion rule (@see BundleInclusionRule for behaviors)
1233         */
1234        public void setBundleInclusionRule(BundleInclusionRule theBundleInclusionRule) {
1235                myBundleInclusionRule = theBundleInclusionRule;
1236        }
1237
1238        /**
1239         * Should the server "pretty print" responses by default (requesting clients can always override this default by
1240         * supplying an <code>Accept</code> header in the request, or a <code>_pretty</code>
1241         * parameter in the request URL.
1242         * <p>
1243         * The default is <code>false</code>
1244         * </p>
1245         * 
1246         * @param theDefaultPrettyPrint
1247         *           The default pretty print setting
1248         */
1249        public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) {
1250                myDefaultPrettyPrint = theDefaultPrettyPrint;
1251        }
1252
1253        /**
1254         * Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
1255         * the <code>_format</code> URL parameter, or with an <code>Accept</code> header in
1256         * the request. The default is {@link EncodingEnum#XML}.
1257         * <p>
1258         * Note when testing this feature: Some browsers will include "application/xml" in their Accept header, which means
1259         * that the
1260         * </p>
1261         */
1262        public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
1263                Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
1264                myDefaultResponseEncoding = theDefaultResponseEncoding;
1265        }
1266
1267        /**
1268         * Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
1269         * {@link #DEFAULT_ETAG_SUPPORT}
1270         * 
1271         * @param theETagSupport
1272         *           The ETag support mode
1273         */
1274        public void setETagSupport(ETagSupportEnum theETagSupport) {
1275                if (theETagSupport == null) {
1276                        throw new NullPointerException("theETagSupport can not be null");
1277                }
1278                myETagSupport = theETagSupport;
1279        }
1280
1281        public void setFhirContext(FhirContext theFhirContext) {
1282                Validate.notNull(theFhirContext, "FhirContext must not be null");
1283                myFhirContext = theFhirContext;
1284        }
1285
1286        /**
1287         * If set to <code>true</code> (the default is <code>true</code>) this server will not
1288         * use the parsed request parameters (URL parameters and HTTP POST form contents) but
1289         * will instead parse these values manually from the request URL and request body.
1290         * <p>
1291         * This is useful because many servlet containers (e.g. Tomcat, Glassfish) will use
1292         * ISO-8859-1 encoding to parse escaped URL characters instead of using UTF-8
1293         * as is specified by FHIR.
1294         * </p>
1295         */
1296        public void setIgnoreServerParsedRequestParameters(boolean theIgnoreServerParsedRequestParameters) {
1297                myIgnoreServerParsedRequestParameters = theIgnoreServerParsedRequestParameters;
1298        }
1299
1300        public void setImplementationDescription(String theImplementationDescription) {
1301                myImplementationDescription = theImplementationDescription;
1302        }
1303
1304        /**
1305         * Sets (or clears) the list of interceptors
1306         *
1307         * @param theList
1308         *           The list of interceptors (may be null)
1309         */
1310        public void setInterceptors(IServerInterceptor... theList) {
1311                myInterceptors.clear();
1312                if (theList != null) {
1313                        myInterceptors.addAll(Arrays.asList(theList));
1314                }
1315        }
1316
1317        /**
1318         * Sets (or clears) the list of interceptors
1319         *
1320         * @param theList
1321         *           The list of interceptors (may be null)
1322         */
1323        public void setInterceptors(List<IServerInterceptor> theList) {
1324                myInterceptors.clear();
1325                if (theList != null) {
1326                        myInterceptors.addAll(theList);
1327                }
1328        }
1329
1330        /**
1331         * Sets the paging provider to use, or <code>null</code> to use no paging (which is the default)
1332         */
1333        public void setPagingProvider(IPagingProvider thePagingProvider) {
1334                myPagingProvider = thePagingProvider;
1335        }
1336
1337        /**
1338         * Sets the non-resource specific providers which implement method calls on this server.
1339         *
1340         * @see #setResourceProviders(Collection)
1341         */
1342        public void setPlainProviders(Collection<Object> theProviders) {
1343                myPlainProviders.clear();
1344                if (theProviders != null) {
1345                        myPlainProviders.addAll(theProviders);
1346                }
1347        }
1348
1349        /**
1350         * Sets the non-resource specific providers which implement method calls on this server.
1351         *
1352         * @see #setResourceProviders(Collection)
1353         */
1354        public void setPlainProviders(Object... theProv) {
1355                setPlainProviders(Arrays.asList(theProv));
1356        }
1357
1358        /**
1359         * Sets the non-resource specific providers which implement method calls on this server
1360         *
1361         * @see #setResourceProviders(Collection)
1362         */
1363        public void setProviders(Object... theProviders) {
1364                myPlainProviders.clear();
1365                if (theProviders != null) {
1366                        myPlainProviders.addAll(Arrays.asList(theProviders));
1367                }
1368        }
1369
1370        /**
1371         * Sets the resource providers for this server
1372         */
1373        public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) {
1374                myResourceProviders.clear();
1375                if (theResourceProviders != null) {
1376                        myResourceProviders.addAll(theResourceProviders);
1377                }
1378        }
1379
1380        /**
1381         * Sets the resource providers for this server
1382         */
1383        public void setResourceProviders(IResourceProvider... theResourceProviders) {
1384                myResourceProviders.clear();
1385                if (theResourceProviders != null) {
1386                        myResourceProviders.addAll(Arrays.asList(theResourceProviders));
1387                }
1388        }
1389
1390        /**
1391         * Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
1392         * server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
1393         */
1394        public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
1395                Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
1396                myServerAddressStrategy = theServerAddressStrategy;
1397        }
1398
1399        /**
1400         * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
1401         * (metadata) statement.
1402         * <p>
1403         * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
1404         * changed, or set to <code>null</code> if you do not wish to export a conformance
1405         * statement.
1406         * </p>
1407         * Note that this method can only be called before the server is initialized.
1408         *
1409         * @throws IllegalStateException
1410         *            Note that this method can only be called prior to {@link #init() initialization} and will throw an
1411         *            {@link IllegalStateException} if called after that.
1412         */
1413        public void setServerConformanceProvider(Object theServerConformanceProvider) {
1414                if (myStarted) {
1415                        throw new IllegalStateException("Server is already started");
1416                }
1417
1418                // call the setRestfulServer() method to point the Conformance
1419                // Provider to this server instance. This is done to avoid
1420                // passing the server into the constructor. Having that sort
1421                // of cross linkage causes reference cycles in Spring wiring
1422                try {
1423                        Method setRestfulServer = theServerConformanceProvider.getClass().getMethod("setRestfulServer", new Class[] { RestfulServer.class });
1424                        if (setRestfulServer != null) {
1425                                setRestfulServer.invoke(theServerConformanceProvider, new Object[] { this });
1426                        }
1427                } catch (Exception e) {
1428                        ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
1429                }
1430                myServerConformanceProvider = theServerConformanceProvider;
1431        }
1432
1433        /**
1434         * Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
1435         * but can be helpful to set with something appropriate.
1436         */
1437        public void setServerName(String theServerName) {
1438                myServerName = theServerName;
1439        }
1440
1441        /**
1442         * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
1443         * but can be helpful to set with something appropriate.
1444         */
1445        public void setServerVersion(String theServerVersion) {
1446                myServerVersion = theServerVersion;
1447        }
1448
1449        /**
1450         * Should the server attempt to decompress incoming request contents (default is <code>true</code>). Typically this
1451         * should be set to <code>true</code> unless the server has other configuration to
1452         * deal with decompressing request bodies (e.g. a filter applied to the whole server).
1453         */
1454        public void setUncompressIncomingContents(boolean theUncompressIncomingContents) {
1455                myUncompressIncomingContents = theUncompressIncomingContents;
1456        }
1457
1458        /**
1459         * @deprecated This feature did not work well, and will be removed. Use {@link ResponseHighlighterInterceptor}
1460         *             instead as an interceptor on your server and it will provide more useful syntax
1461         *             highlighting. Deprocated in 1.4
1462         */
1463        @Deprecated
1464        public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
1465                myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
1466        }
1467
1468        public void unregisterInterceptor(IServerInterceptor theInterceptor) {
1469                Validate.notNull(theInterceptor, "Interceptor can not be null");
1470                myInterceptors.remove(theInterceptor);
1471        }
1472
1473        /**
1474         * Unregister one provider (either a Resource provider or a plain provider)
1475         * 
1476         * @param provider
1477         * @throws Exception
1478         */
1479        public void unregisterProvider(Object provider) throws Exception {
1480                if (provider != null) {
1481                        Collection<Object> providerList = new ArrayList<Object>(1);
1482                        providerList.add(provider);
1483                        unregisterProviders(providerList);
1484                }
1485        }
1486
1487        /**
1488         * Unregister a {@code Collection} of providers
1489         * 
1490         * @param providers
1491         * @throws Exception
1492         */
1493        public void unregisterProviders(Collection<? extends Object> providers) throws Exception {
1494                ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
1495                if (providers != null) {
1496                        for (Object provider : providers) {
1497                                removeResourceMethods(provider);
1498                                if (provider instanceof IResourceProvider) {
1499                                        myResourceProviders.remove(provider);
1500                                        IResourceProvider rsrcProvider = (IResourceProvider) provider;
1501                                        Class<? extends IBaseResource> resourceType = rsrcProvider.getResourceType();
1502                                        String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
1503                                        myTypeToProvider.remove(resourceName);
1504                                        providedResourceScanner.removeProvidedResources(rsrcProvider);
1505                                } else {
1506                                        myPlainProviders.remove(provider);
1507                                }
1508                                invokeDestroy(provider);
1509                        }
1510                }
1511        }
1512
1513        private void writeExceptionToResponse(HttpServletResponse theResponse, BaseServerResponseException theException) throws IOException {
1514                theResponse.setStatus(theException.getStatusCode());
1515                addHeadersToResponse(theResponse);
1516                if (theException.hasResponseHeaders()) {
1517                        for (Entry<String, List<String>> nextEntry : theException.getResponseHeaders().entrySet()) {
1518                                for (String nextValue : nextEntry.getValue()) {
1519                                        if (isNotBlank(nextValue)) {
1520                                                theResponse.addHeader(nextEntry.getKey(), nextValue);
1521                                        }
1522                                }
1523                        }
1524                }
1525                theResponse.setContentType("text/plain");
1526                theResponse.setCharacterEncoding("UTF-8");
1527                theResponse.getWriter().write(theException.getMessage());
1528        }
1529
1530        private static boolean partIsOperation(String nextString) {
1531                return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
1532        }
1533
1534}