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 private static Reader createRequestReader(RequestDetails theRequest, Charset charset) { 124 return new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset); 125 } 126 127 // Do not make private 128 @SuppressWarnings("WeakerAccess") 129 public static Reader createRequestReader(RequestDetails theRequest) { 130 return createRequestReader(theRequest, determineRequestCharset(theRequest)); 131 } 132 133 public static Charset determineRequestCharset(RequestDetails theRequest) { 134 Charset charset = theRequest.getCharset(); 135 if (charset == null) { 136 charset = Charset.forName("UTF-8"); 137 } 138 return charset; 139 } 140 141 @SuppressWarnings("unchecked") 142 static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) { 143 FhirContext ctx = theRequest.getServer().getFhirContext(); 144 145 final Charset charset = determineRequestCharset(theRequest); 146 Reader requestReader = createRequestReader(theRequest, charset); 147 148 RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null; 149 150 EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); 151 if (encoding == null) { 152 String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); 153 if (ctValue != null) { 154 if (ctValue.startsWith("application/x-www-form-urlencoded")) { 155 String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); 156 throw new InvalidRequestException(msg); 157 } 158 } 159 if (isBlank(ctValue)) { 160 String body; 161 try { 162 body = IOUtils.toString(requestReader); 163 } catch (IOException e) { 164 // This shouldn't happen since we're reading from a byte array.. 165 throw new InternalErrorException(e); 166 } 167 if (isBlank(body)) { 168 return null; 169 } 170 171 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); 172 throw new InvalidRequestException(msg); 173 } else { 174 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); 175 throw new InvalidRequestException(msg); 176 } 177 } 178 179 IParser parser = encoding.newParser(ctx); 180 parser.setServerBaseUrl(theRequest.getFhirServerBase()); 181 T retVal; 182 try { 183 if (theResourceType != null) { 184 retVal = parser.parseResource(theResourceType, requestReader); 185 } else { 186 retVal = (T) parser.parseResource(requestReader); 187 } 188 } catch (DataFormatException e) { 189 String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "failedToParseRequest", encoding.name(), e.getMessage()); 190 throw new InvalidRequestException(msg); 191 } 192 193 return retVal; 194 } 195 196 static IBaseResource parseResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) { 197 if (theRequest.getResource() != null) { 198 return theRequest.getResource(); 199 } 200 201 IBaseResource retVal = null; 202 203 if (theResourceType != null && IBaseBinary.class.isAssignableFrom(theResourceType)) { 204 String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); 205 if (EncodingEnum.forContentTypeStrict(ct) == null) { 206 FhirContext ctx = theRequest.getServer().getFhirContext(); 207 IBaseBinary binary = BinaryUtil.newBinary(ctx); 208 binary.setId(theRequest.getId()); 209 binary.setContentType(ct); 210 binary.setContent(theRequest.loadRequestContents()); 211 retVal = binary; 212 213 /* 214 * Security context header, which is only in 215 * DSTU3+ 216 */ 217 if (ctx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { 218 String securityContext = theRequest.getHeader(Constants.HEADER_X_SECURITY_CONTEXT); 219 if (isNotBlank(securityContext)) { 220 BinaryUtil.setSecurityContext(ctx, binary, securityContext); 221 } 222 } 223 } 224 } 225 226 if (retVal == null) { 227 retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); 228 } 229 230 theRequest.setResource(retVal); 231 232 return retVal; 233 } 234 235}