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