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