001package ca.uhn.fhir.rest.server.method; 002 003/* 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2019 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 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.context.FhirVersionEnum; 025import ca.uhn.fhir.parser.DataFormatException; 026import ca.uhn.fhir.parser.IParser; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.api.EncodingEnum; 029import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 030import ca.uhn.fhir.rest.api.server.RequestDetails; 031import ca.uhn.fhir.rest.server.IResourceProvider; 032import ca.uhn.fhir.rest.server.RestfulServerUtils; 033import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 034import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 035import ca.uhn.fhir.util.BinaryUtil; 036import org.apache.commons.io.IOUtils; 037import org.apache.commons.lang3.Validate; 038import org.hl7.fhir.instance.model.api.IBaseBinary; 039import org.hl7.fhir.instance.model.api.IBaseResource; 040 041import javax.annotation.Nonnull; 042import java.io.ByteArrayInputStream; 043import java.io.IOException; 044import java.io.InputStreamReader; 045import java.io.Reader; 046import java.lang.reflect.Method; 047import java.lang.reflect.Modifier; 048import java.nio.charset.Charset; 049import java.util.Collection; 050 051import static org.apache.commons.lang3.StringUtils.isBlank; 052import static org.apache.commons.lang3.StringUtils.isNotBlank; 053 054public class ResourceParameter implements IParameter { 055 056 private final boolean myMethodIsOperation; 057 private Mode myMode; 058 private Class<? extends IBaseResource> myResourceType; 059 060 public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode, boolean theMethodIsOperation) { 061 Validate.notNull(theParameterType, "theParameterType can not be null"); 062 Validate.notNull(theMode, "theMode can not be null"); 063 064 myResourceType = theParameterType; 065 myMode = theMode; 066 myMethodIsOperation = theMethodIsOperation; 067 068 Class<? extends IBaseResource> providerResourceType = null; 069 if (theProvider instanceof IResourceProvider) { 070 providerResourceType = ((IResourceProvider) theProvider).getResourceType(); 071 } 072 073 if (Modifier.isAbstract(myResourceType.getModifiers()) && providerResourceType != null) { 074 myResourceType = providerResourceType; 075 } 076 077 } 078 079 public Mode getMode() { 080 return myMode; 081 } 082 083 public Class<? extends IBaseResource> getResourceType() { 084 return myResourceType; 085 } 086 087 @Override 088 public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) { 089 // ignore for now 090 } 091 092 093 @Override 094 public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException { 095 switch (myMode) { 096 case BODY: 097 try { 098 return IOUtils.toString(createRequestReader(theRequest)); 099 } catch (IOException e) { 100 // Shouldn't happen since we're reading from a byte array 101 throw new InternalErrorException("Failed to load request", e); 102 } 103 case BODY_BYTE_ARRAY: 104 return theRequest.loadRequestContents(); 105 case ENCODING: 106 return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); 107 case RESOURCE: 108 default: 109 Class<? extends IBaseResource> resourceTypeToParse = myResourceType; 110 if (myMethodIsOperation) { 111 // Operations typically have a Parameters resource as the body 112 resourceTypeToParse = null; 113 } 114 return parseResourceFromRequest(theRequest, theMethodBinding, resourceTypeToParse); 115 } 116 // } 117 } 118 119 public enum Mode { 120 BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE 121 } 122 123 public static Reader createRequestReader(RequestDetails theRequest, Charset charset) { 124 Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset); 125 return requestReader; 126 } 127 128 public static Reader createRequestReader(RequestDetails theRequest) { 129 return createRequestReader(theRequest, determineRequestCharset(theRequest)); 130 } 131 132 public static Charset determineRequestCharset(RequestDetails theRequest) { 133 Charset charset = theRequest.getCharset(); 134 if (charset == null) { 135 charset = Charset.forName("UTF-8"); 136 } 137 return charset; 138 } 139 140 @SuppressWarnings("unchecked") 141 public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) { 142 FhirContext ctx = theRequest.getServer().getFhirContext(); 143 144 final Charset charset = determineRequestCharset(theRequest); 145 Reader requestReader = createRequestReader(theRequest, charset); 146 147 RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null; 148 149 EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); 150 if (encoding == null) { 151 String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); 152 if (ctValue != null) { 153 if (ctValue.startsWith("application/x-www-form-urlencoded")) { 154 String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); 155 throw new InvalidRequestException(msg); 156 } 157 } 158 if (isBlank(ctValue)) { 159 /* 160 * If the client didn't send a content type, try to guess 161 */ 162 String body; 163 try { 164 body = IOUtils.toString(requestReader); 165 } catch (IOException e) { 166 // This shouldn't happen since we're reading from a byte array.. 167 throw new InternalErrorException(e); 168 } 169 if (isBlank(body)) { 170 return null; 171 } 172 encoding = EncodingEnum.detectEncodingNoDefault(body); 173 if (encoding == null) { 174 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); 175 throw new InvalidRequestException(msg); 176 } 177 requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset); 178 } else { 179 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); 180 throw new InvalidRequestException(msg); 181 } 182 } 183 184 IParser parser = encoding.newParser(ctx); 185 parser.setServerBaseUrl(theRequest.getFhirServerBase()); 186 T retVal; 187 try { 188 if (theResourceType != null) { 189 retVal = parser.parseResource(theResourceType, requestReader); 190 } else { 191 retVal = (T) parser.parseResource(requestReader); 192 } 193 } catch (DataFormatException e) { 194 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "failedToParseRequest", encoding.name(), e.getMessage()); 195 throw new InvalidRequestException(msg); 196 } 197 198 return retVal; 199 } 200 201 public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) { 202 IBaseResource retVal = null; 203 204 if (theResourceType != null && IBaseBinary.class.isAssignableFrom(theResourceType)) { 205 String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); 206 if (EncodingEnum.forContentTypeStrict(ct) == null) { 207 FhirContext ctx = theRequest.getServer().getFhirContext(); 208 IBaseBinary binary = BinaryUtil.newBinary(ctx); 209 binary.setId(theRequest.getId()); 210 binary.setContentType(ct); 211 binary.setContent(theRequest.loadRequestContents()); 212 retVal = binary; 213 214 /* 215 * Security context header, which is only in 216 * DSTU3+ 217 */ 218 if (ctx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 219 String securityContext = theRequest.getHeader(Constants.HEADER_X_SECURITY_CONTEXT); 220 if (isNotBlank(securityContext)) { 221 BinaryUtil.setSecurityContext(ctx, binary, securityContext); 222 } 223 } 224 } 225 } 226 227 if (retVal == null) { 228 retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); 229 } 230 return retVal; 231 } 232 233}