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}