001package ca.uhn.fhir.rest.method; 002 003import static org.apache.commons.lang3.StringUtils.isBlank; 004/* 005 * #%L 006 * HAPI FHIR - Core Library 007 * %% 008 * Copyright (C) 2014 - 2017 University Health Network 009 * %% 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * #L% 022 */ 023import static org.apache.commons.lang3.StringUtils.isNotBlank; 024 025import java.lang.reflect.Method; 026import java.util.Collections; 027import java.util.Set; 028 029import org.hl7.fhir.instance.model.api.IBaseResource; 030import org.hl7.fhir.instance.model.api.IIdType; 031 032import ca.uhn.fhir.context.FhirContext; 033import ca.uhn.fhir.model.api.IResource; 034import ca.uhn.fhir.model.primitive.IdDt; 035import ca.uhn.fhir.rest.annotation.Update; 036import ca.uhn.fhir.rest.api.MethodOutcome; 037import ca.uhn.fhir.rest.api.RequestTypeEnum; 038import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 039import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; 040import ca.uhn.fhir.rest.server.Constants; 041import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 042 043public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { 044 045 private Integer myIdParameterIndex; 046 047 public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { 048 super(theMethod, theContext, Update.class, theProvider); 049 050 myIdParameterIndex = MethodUtil.findIdParameterIndex(theMethod, getContext()); 051 } 052 053 @Override 054 protected void addParametersForServerRequest(RequestDetails theRequest, Object[] theParams) { 055 /* 056 * We are being a bit lenient here, since technically the client is supposed to include the version in the 057 * Content-Location header, but we allow it in the PUT URL as well.. 058 */ 059 String locationHeader = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); 060 IIdType id = theRequest.getId(); 061 if (isNotBlank(locationHeader)) { 062 id.setValue(locationHeader); 063 if (isNotBlank(id.getResourceType())) { 064 if (!getResourceName().equals(id.getResourceType())) { 065 throw new InvalidRequestException( 066 "Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader); 067 } 068 } 069 } 070 071 id = applyETagAsVersion(theRequest, id); 072 073 if (theRequest.getId() != null && theRequest.getId().hasVersionIdPart() == false) { 074 if (id != null && id.hasVersionIdPart()) { 075 theRequest.getId().setValue(id.getValue()); 076 } 077 } 078 079 if (isNotBlank(locationHeader)) { 080 MethodOutcome mo = new MethodOutcome(); 081 parseContentLocation(getContext(), mo, locationHeader); 082 if (mo.getId() == null || mo.getId().isEmpty()) { 083 throw new InvalidRequestException("Invalid Content-Location header for resource " + getResourceName() + ": " + locationHeader); 084 } 085 } 086 087 super.addParametersForServerRequest(theRequest, theParams); 088 } 089 090 public static IIdType applyETagAsVersion(RequestDetails theRequest, IIdType id) { 091 String ifMatchValue = theRequest.getHeader(Constants.HEADER_IF_MATCH); 092 if (isNotBlank(ifMatchValue)) { 093 ifMatchValue = MethodUtil.parseETagValue(ifMatchValue); 094 if (id != null && id.hasVersionIdPart() == false) { 095 id = id.withVersion(ifMatchValue); 096 } 097 } 098 return id; 099 } 100 101 @Override 102 protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) { 103 IdDt idDt = (IdDt) theArgs[myIdParameterIndex]; 104 if (idDt == null) { 105 throw new NullPointerException("ID can not be null"); 106 } 107 108 FhirContext context = getContext(); 109 110 HttpPutClientInvocation retVal = MethodUtil.createUpdateInvocation(theResource, null, idDt, context); 111 112 for (int idx = 0; idx < theArgs.length; idx++) { 113 IParameter nextParam = getParameters().get(idx); 114 nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null); 115 } 116 117 return retVal; 118 } 119 120 @Override 121 protected String getMatchingOperation() { 122 return null; 123 } 124 125 @Override 126 public RestOperationTypeEnum getRestOperationType() { 127 return RestOperationTypeEnum.UPDATE; 128 } 129 130 /* 131 * @Override public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { if 132 * (super.incomingServerRequestMatchesMethod(theRequest)) { if (myVersionIdParameterIndex != null) { if 133 * (theRequest.getVersionId() == null) { return false; } } else { if (theRequest.getVersionId() != null) { return 134 * false; } } return true; } else { return false; } } 135 */ 136 137 @Override 138 protected Set<RequestTypeEnum> provideAllowableRequestTypes() { 139 return Collections.singleton(RequestTypeEnum.PUT); 140 } 141 142 @Override 143 protected void validateResourceIdAndUrlIdForNonConditionalOperation(IBaseResource theResource, String theResourceId, String theUrlId, String theMatchUrl) { 144 if (isBlank(theMatchUrl)) { 145 if (isBlank(theUrlId)) { 146 String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInUrlForUpdate"); 147 throw new InvalidRequestException(msg); 148 } 149 if (isBlank(theResourceId)) { 150 String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "noIdInBodyForUpdate"); 151 throw new InvalidRequestException(msg); 152 } 153 if (!theResourceId.equals(theUrlId)) { 154 String msg = getContext().getLocalizer().getMessage(BaseOutcomeReturningMethodBindingWithResourceParam.class, "incorrectIdForUpdate", theResourceId, theUrlId); 155 throw new InvalidRequestException(msg); 156 } 157 } else { 158 theResource.setId((IIdType)null); 159 } 160 161 } 162}