001package ca.uhn.fhir.test.utilities.server;
002
003/*-
004 * #%L
005 * HAPI FHIR Test Utilities
006 * %%
007 * Copyright (C) 2014 - 2023 Smile CDR, Inc.
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 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.context.FhirVersionEnum;
025import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
026import ca.uhn.fhir.interceptor.api.IInterceptorService;
027import ca.uhn.fhir.interceptor.api.Pointcut;
028import ca.uhn.fhir.rest.api.EncodingEnum;
029import ca.uhn.fhir.rest.client.api.IGenericClient;
030import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
031import ca.uhn.fhir.rest.server.IPagingProvider;
032import ca.uhn.fhir.rest.server.RestfulServer;
033import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
034import ca.uhn.fhir.validation.FhirValidator;
035import ca.uhn.fhir.validation.ResultSeverityEnum;
036import org.apache.commons.lang3.Validate;
037import org.apache.commons.lang3.time.DateUtils;
038import org.junit.jupiter.api.extension.ExtensionContext;
039
040import javax.servlet.http.HttpServlet;
041import java.util.ArrayList;
042import java.util.Arrays;
043import java.util.HashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.function.Consumer;
047
048public class RestfulServerExtension extends BaseJettyServerExtension<RestfulServerExtension> {
049        private FhirContext myFhirContext;
050        private List<Object> myProviders = new ArrayList<>();
051        private FhirVersionEnum myFhirVersion;
052        private RestfulServer myServlet;
053        private List<Consumer<RestfulServer>> myConsumers = new ArrayList<>();
054        private Map<String, Object> myRunningServerUserData = new HashMap<>();
055        private ServerValidationModeEnum myServerValidationMode = ServerValidationModeEnum.NEVER;
056        private IPagingProvider myPagingProvider;
057
058        /**
059         * Constructor
060         */
061        public RestfulServerExtension(FhirContext theFhirContext, Object... theProviders) {
062                Validate.notNull(theFhirContext);
063                myFhirContext = theFhirContext;
064                if (theProviders != null) {
065                        myProviders = new ArrayList<>(Arrays.asList(theProviders));
066                }
067        }
068
069        /**
070         * Constructor: If this is used, it will create and tear down a FhirContext which is good for memory
071         */
072        public RestfulServerExtension(FhirVersionEnum theFhirVersionEnum) {
073                Validate.notNull(theFhirVersionEnum);
074                myFhirVersion = theFhirVersionEnum;
075        }
076
077        /**
078         * User data map which is automatically cleared when the server is stopped
079         */
080        public Map<String, Object> getRunningServerUserData() {
081                return myRunningServerUserData;
082        }
083
084        @Override
085        protected void startServer() throws Exception {
086                super.startServer();
087
088                myFhirContext.getRestfulClientFactory().setSocketTimeout((int) (500 * DateUtils.MILLIS_PER_SECOND));
089                myFhirContext.getRestfulClientFactory().setServerValidationMode(myServerValidationMode);
090        }
091
092        @Override
093        protected HttpServlet provideServlet() {
094                if (myServlet == null) {
095                        myServlet = new RestfulServer(myFhirContext);
096                        myServlet.setDefaultPrettyPrint(true);
097                        if (myProviders != null) {
098                                myServlet.registerProviders(myProviders);
099                        }
100                        if (myPagingProvider != null) {
101                                myServlet.setPagingProvider(myPagingProvider);
102                        }
103
104                        myConsumers.forEach(t -> t.accept(myServlet));
105                }
106
107                return myServlet;
108        }
109
110        @Override
111        public void stopServer() throws Exception {
112                super.stopServer();
113                if (!isRunning()) {
114                        return;
115                }
116                myRunningServerUserData.clear();
117                myPagingProvider = null;
118                myServlet = null;
119        }
120
121
122        private void createContextIfNeeded() {
123                if (myFhirVersion != null) {
124                        myFhirContext = FhirContext.forCached(myFhirVersion);
125                }
126        }
127
128        /**
129         * Creates a new client for each callof this method
130         */
131        public IGenericClient getFhirClient() {
132                return myFhirContext.newRestfulGenericClient(getBaseUrl());
133        }
134
135        public FhirContext getFhirContext() {
136                createContextIfNeeded();
137                return myFhirContext;
138        }
139
140        public RestfulServer getRestfulServer() {
141                return myServlet;
142        }
143
144        @Override
145        public void beforeEach(ExtensionContext theContext) throws Exception {
146                createContextIfNeeded();
147                super.beforeEach(theContext);
148        }
149
150        public RestfulServerExtension registerProvider(Object theProvider) {
151                Validate.notNull(theProvider);
152                if (isStarted()) {
153                        myServlet.registerProvider(theProvider);
154                } else {
155                        myProviders.add(theProvider);
156                }
157                return this;
158        }
159
160        public RestfulServerExtension withServer(Consumer<RestfulServer> theConsumer) {
161                if (isStarted()) {
162                        theConsumer.accept(myServlet);
163                } else {
164                        myConsumers.add(theConsumer);
165                }
166                return this;
167        }
168
169        private boolean isStarted() {
170                return myServlet != null;
171        }
172
173        public RestfulServerExtension registerInterceptor(Object theInterceptor) {
174                return withServer(t -> t.getInterceptorService().registerInterceptor(theInterceptor));
175        }
176
177        public RestfulServerExtension withValidationMode(ServerValidationModeEnum theValidationMode) {
178                myServerValidationMode = theValidationMode;
179                return this;
180        }
181
182        public void unregisterAllInterceptors() {
183                myServlet.getInterceptorService().unregisterAllInterceptors();
184        }
185
186        public RestfulServerExtension withPagingProvider(IPagingProvider thePagingProvider) {
187                if (isStarted()) {
188                        myServlet.setPagingProvider(thePagingProvider);
189                } else {
190                        myPagingProvider = thePagingProvider;
191                }
192                return this;
193        }
194
195        public RestfulServerExtension unregisterInterceptor(Object theInterceptor) {
196                return withServer(t -> t.getInterceptorService().unregisterInterceptor(theInterceptor));
197        }
198
199        public void unregisterProvider(Object theProvider) {
200                withServer(t -> t.unregisterProvider(theProvider));
201        }
202
203        public Integer getDefaultPageSize() {
204                return myServlet.getDefaultPageSize();
205        }
206
207        public void setDefaultPageSize(Integer theInitialDefaultPageSize) {
208                myServlet.setDefaultPageSize(theInitialDefaultPageSize);
209        }
210
211        public IInterceptorService getInterceptorService() {
212                return myServlet.getInterceptorService();
213        }
214
215        public RestfulServerExtension registerAnonymousInterceptor(Pointcut thePointcut, IAnonymousInterceptor theInterceptor) {
216                return withServer(t -> t.getInterceptorService().registerAnonymousInterceptor(thePointcut, theInterceptor));
217        }
218
219        public RestfulServerExtension withDefaultResponseEncoding(EncodingEnum theEncodingEnum) {
220                return withServer(t -> t.setDefaultResponseEncoding(theEncodingEnum));
221        }
222}