001package ca.uhn.fhir.jaxrs.server;
002
003import java.io.IOException;
004/*
005 * #%L
006 * HAPI FHIR JAX-RS Server
007 * %%
008 * Copyright (C) 2014 - 2016 University Health Network
009 * %%
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 * 
014 * http://www.apache.org/licenses/LICENSE-2.0
015 * 
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 * #L%
022 */
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Map.Entry;
028
029import javax.ws.rs.core.Context;
030import javax.ws.rs.core.HttpHeaders;
031import javax.ws.rs.core.MultivaluedMap;
032import javax.ws.rs.core.Response;
033import javax.ws.rs.core.UriInfo;
034
035import org.apache.commons.lang3.exception.ExceptionUtils;
036import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
037
038import ca.uhn.fhir.context.FhirContext;
039import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
040import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
041import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
042import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
043import ca.uhn.fhir.parser.DataFormatException;
044import ca.uhn.fhir.rest.api.RequestTypeEnum;
045import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
046import ca.uhn.fhir.rest.server.AddProfileTagEnum;
047import ca.uhn.fhir.rest.server.ETagSupportEnum;
048import ca.uhn.fhir.rest.server.EncodingEnum;
049import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
050import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
051import ca.uhn.fhir.rest.server.IServerAddressStrategy;
052import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
053import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
054import ca.uhn.fhir.util.OperationOutcomeUtil;
055
056/**
057 * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing
058 * the IRestfulServerDefaults interface and exposes the uri and headers.
059 *
060 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
061 */
062public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
063
064    private final FhirContext CTX;
065
066    /** The default exception interceptor */
067    private static final JaxRsExceptionInterceptor DEFAULT_EXCEPTION_HANDLER = new JaxRsExceptionInterceptor();
068    private static final String PROCESSING = "processing";
069    private static final String ERROR = "error";
070
071    /** the uri info */
072    @Context
073    private UriInfo theUriInfo;
074    /** the http headers */
075    @Context
076    private HttpHeaders theHeaders;
077
078    @Override
079    public FhirContext getFhirContext() {
080        return CTX;
081    }
082
083    /**
084     * Default is DSTU2.  Use {@link AbstractJaxRsProvider#AbstractJaxRsProvider(FhirContext)} to specify a DSTU3 context.
085     */
086    protected AbstractJaxRsProvider() {
087        CTX = FhirContext.forDstu2();
088    }
089
090    /**
091     *
092     * @param ctx the {@link FhirContext} to support.
093     */
094    protected AbstractJaxRsProvider(final FhirContext ctx) {
095        CTX = ctx;
096    }
097
098    /**
099     * This method returns the query parameters
100     * @return the query parameters
101     */
102    public Map<String, String[]> getParameters() {
103        final MultivaluedMap<String, String> queryParameters = getUriInfo().getQueryParameters();
104        final HashMap<String, String[]> params = new HashMap<String, String[]>();
105        for (final Entry<String, List<String>> paramEntry : queryParameters.entrySet()) {
106            params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()]));
107        }
108        return params;
109    }
110
111    /**
112     * This method returns the default server address strategy. The default strategy return the
113     * base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()}
114     * @return
115     */
116    public IServerAddressStrategy getServerAddressStrategy() {
117        final HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy();
118        addressStrategy.setValue(getBaseForRequest());
119        return addressStrategy;
120    }
121
122    /**
123     * This method returns the server base, independent of the request or resource.
124     * @see javax.ws.rs.core.UriInfo#getBaseUri()
125     * @return the ascii string for the server base
126     */
127    public String getBaseForServer() {
128        return getUriInfo().getBaseUri().toASCIIString();
129    }
130
131    /**
132     * This method returns the server base, including the resource path.
133     * {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()}
134     * @return the ascii string for the base resource provider path
135     */
136    public String getBaseForRequest() {
137        return getBaseForServer();
138    }
139
140    /**
141     * Default: an empty list of interceptors (Interceptors are not yet supported 
142     * in the JAX-RS server). Please get in touch if you'd like to help!
143     * 
144     * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
145     */
146    @Override
147    public List<IServerInterceptor> getInterceptors() {
148        return Collections.emptyList();
149    }
150
151    /**
152     * Get the uriInfo
153     * @return the uri info
154     */
155    public UriInfo getUriInfo() {
156        return this.theUriInfo;
157    }
158
159    /**
160     * Set the Uri Info
161     * @param uriInfo the uri info
162     */
163    public void setUriInfo(final UriInfo uriInfo) {
164        this.theUriInfo = uriInfo;
165    }
166
167    /**
168     * Get the headers
169     * @return the headers
170     */
171    public HttpHeaders getHeaders() {
172        return this.theHeaders;
173    }
174
175    /**
176     * Set the headers
177     * @param headers the headers to set 
178     */
179    public void setHeaders(final HttpHeaders headers) {
180        this.theHeaders = headers;
181    }
182
183    /**
184     * Return the requestbuilder for the server
185     * @param requestType the type of the request
186     * @param restOperation the rest operation type
187     * @param theResourceName the resource name
188     * @return the requestbuilder
189     */
190    public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation, final String theResourceName) {
191        return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString(), theResourceName);
192    }
193
194    /**
195     * Return the requestbuilder for the server
196     * @param requestType the type of the request
197     * @param restOperation the rest operation type
198     * @return the requestbuilder
199     */
200    public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) {
201        return getRequest(requestType, restOperation, null);
202    }
203
204    /**
205     * DEFAULT = EncodingEnum.JSON
206     */
207    @Override
208    public EncodingEnum getDefaultResponseEncoding() {
209        return EncodingEnum.JSON;
210    }
211
212    /**
213     * DEFAULT = true
214     */
215    @Override
216    public boolean isDefaultPrettyPrint() {
217        return true;
218    }
219
220    /**
221     * DEFAULT = ETagSupportEnum.DISABLED
222     */
223    @Override
224    public ETagSupportEnum getETagSupport() {
225        return ETagSupportEnum.DISABLED;
226    }
227
228    /**
229     * DEFAULT = AddProfileTagEnum.NEVER
230     */
231    @Override
232    public AddProfileTagEnum getAddProfileTag() {
233        return AddProfileTagEnum.NEVER;
234    }
235
236    /**
237     * DEFAULT = false
238     */
239    @Override
240    public boolean isUseBrowserFriendlyContentTypes() {
241        return true;
242    }
243
244    /**
245     * DEFAULT = false
246     */
247    public boolean withStackTrace() {
248        return false;
249    }
250
251    /**
252     * Convert an exception to a response
253     * @param theRequest the incoming request
254     * @param theException the exception to convert
255     * @return response
256     * @throws IOException
257     */
258    public Response handleException(final JaxRsRequest theRequest, final Throwable theException)
259            throws IOException {
260        if (theException instanceof JaxRsResponseException) {
261            return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, (JaxRsResponseException) theException);
262        } else if (theException instanceof DataFormatException) {
263            return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, new JaxRsResponseException(
264                    new InvalidRequestException(theException.getMessage(), createOutcome((DataFormatException) theException))));
265        } else {
266            return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest,
267                    DEFAULT_EXCEPTION_HANDLER.convertException(this, theException));
268        }
269    }
270
271    private IBaseOperationOutcome createOutcome(final DataFormatException theException) {
272        final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext());
273        final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException);
274        OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING);
275        return oo;
276    }
277}