
001/* 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2023 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.server.method; 021 022import ca.uhn.fhir.i18n.Msg; 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.RequestTypeEnum; 030import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 031import ca.uhn.fhir.rest.api.server.RequestDetails; 032import ca.uhn.fhir.rest.server.IResourceProvider; 033import ca.uhn.fhir.rest.server.RestfulServerUtils; 034import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 035import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 036import ca.uhn.fhir.util.BinaryUtil; 037import org.apache.commons.io.IOUtils; 038import org.apache.commons.lang3.Validate; 039import org.hl7.fhir.instance.model.api.IBaseBinary; 040import org.hl7.fhir.instance.model.api.IBaseResource; 041 042import javax.annotation.Nonnull; 043import java.io.ByteArrayInputStream; 044import java.io.IOException; 045import java.io.InputStreamReader; 046import java.io.Reader; 047import java.lang.reflect.Method; 048import java.lang.reflect.Modifier; 049import java.nio.charset.Charset; 050import java.util.Collection; 051 052import static org.apache.commons.lang3.StringUtils.isBlank; 053import static org.apache.commons.lang3.StringUtils.isNotBlank; 054 055public class ResourceParameter implements IParameter { 056 057 private final boolean myMethodIsOperationOrPatch; 058 private Mode myMode; 059 private Class<? extends IBaseResource> myResourceType; 060 061 public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode, boolean theMethodIsOperation, boolean theMethodIsPatch) { 062 Validate.notNull(theParameterType, "theParameterType can not be null"); 063 Validate.notNull(theMode, "theMode can not be null"); 064 065 myResourceType = theParameterType; 066 myMode = theMode; 067 myMethodIsOperationOrPatch = theMethodIsOperation || theMethodIsPatch; 068 069 Class<? extends IBaseResource> providerResourceType = null; 070 if (theProvider instanceof IResourceProvider) { 071 providerResourceType = ((IResourceProvider) theProvider).getResourceType(); 072 } 073 074 if (Modifier.isAbstract(myResourceType.getModifiers()) && providerResourceType != null) { 075 myResourceType = providerResourceType; 076 } 077 078 } 079 080 public Mode getMode() { 081 return myMode; 082 } 083 084 public Class<? extends IBaseResource> getResourceType() { 085 return myResourceType; 086 } 087 088 @Override 089 public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) { 090 // ignore for now 091 } 092 093 094 @Override 095 public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { 096 switch (myMode) { 097 case BODY: 098 try { 099 return IOUtils.toString(createRequestReader(theRequest)); 100 } catch (IOException e) { 101 // Shouldn't happen since we're reading from a byte array 102 throw new InternalErrorException(Msg.code(445) + "Failed to load request", e); 103 } 104 case BODY_BYTE_ARRAY: 105 return theRequest.loadRequestContents(); 106 case ENCODING: 107 return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); 108 case RESOURCE: 109 default: 110 Class<? extends IBaseResource> resourceTypeToParse = myResourceType; 111 if (myMethodIsOperationOrPatch) { 112 // Operations typically have a Parameters resource as the body 113 resourceTypeToParse = null; 114 } 115 return parseResourceFromRequest(theRequest, theMethodBinding, resourceTypeToParse); 116 } 117 // } 118 } 119 120 public enum Mode { 121 BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE 122 } 123 124 private static Reader createRequestReader(RequestDetails theRequest, Charset charset) { 125 return new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset); 126 } 127 128 // Do not make private 129 @SuppressWarnings("WeakerAccess") 130 public static Reader createRequestReader(RequestDetails theRequest) { 131 return createRequestReader(theRequest, determineRequestCharset(theRequest)); 132 } 133 134 public static Charset determineRequestCharset(RequestDetails theRequest) { 135 Charset charset = theRequest.getCharset(); 136 if (charset == null) { 137 charset = Charset.forName("UTF-8"); 138 } 139 return charset; 140 } 141 142 @SuppressWarnings("unchecked") 143 static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding theMethodBinding, Class<T> theResourceType) { 144 FhirContext ctx = theRequest.getServer().getFhirContext(); 145 146 final Charset charset = determineRequestCharset(theRequest); 147 Reader requestReader = createRequestReader(theRequest, charset); 148 149 RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null; 150 151 EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); 152 if (encoding == null) { 153 String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); 154 if (ctValue != null) { 155 if (ctValue.startsWith("application/x-www-form-urlencoded")) { 156 String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); 157 throw new InvalidRequestException(Msg.code(446) + msg); 158 } 159 } 160 if (isBlank(ctValue)) { 161 String body; 162 try { 163 body = IOUtils.toString(requestReader); 164 } catch (IOException e) { 165 // This shouldn't happen since we're reading from a byte array.. 166 throw new InternalErrorException(Msg.code(447) + e); 167 } 168 if (isBlank(body)) { 169 return null; 170 } 171 172 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); 173 throw new InvalidRequestException(Msg.code(448) + msg); 174 } else { 175 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); 176 throw new InvalidRequestException(Msg.code(449) + msg); 177 } 178 } 179 180 IParser parser = encoding.newParser(ctx); 181 parser.setServerBaseUrl(theRequest.getFhirServerBase()); 182 T retVal; 183 try { 184 if (theResourceType != null) { 185 retVal = parser.parseResource(theResourceType, requestReader); 186 } else { 187 retVal = (T) parser.parseResource(requestReader); 188 } 189 } catch (DataFormatException e) { 190 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "failedToParseRequest", encoding.name(), e.getMessage()); 191 throw new InvalidRequestException(Msg.code(450) + msg); 192 } 193 194 return retVal; 195 } 196 197 static IBaseResource parseResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding theMethodBinding, Class<? extends IBaseResource> theResourceType) { 198 if (theRequest.getResource() != null) { 199 return theRequest.getResource(); 200 } 201 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 boolean isNonFhirPatch = false; 228 if (theRequest.getRequestType() == RequestTypeEnum.PATCH) { 229 EncodingEnum requestEncoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest, true); 230 if (requestEncoding == null) { 231 isNonFhirPatch = true; 232 } 233 } 234 235 if (retVal == null && !isNonFhirPatch) { 236 retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); 237 } 238 239 theRequest.setResource(retVal); 240 241 return retVal; 242 } 243 244}