
001package ca.uhn.fhir.rest.server.method; 002 003/* 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2022 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.model.api.IResource; 026import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 027import ca.uhn.fhir.model.primitive.IdDt; 028import ca.uhn.fhir.model.valueset.BundleTypeEnum; 029import ca.uhn.fhir.rest.annotation.History; 030import ca.uhn.fhir.rest.api.Constants; 031import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 032import ca.uhn.fhir.rest.api.server.IBundleProvider; 033import ca.uhn.fhir.rest.api.server.IRestfulServer; 034import ca.uhn.fhir.rest.api.server.RequestDetails; 035import ca.uhn.fhir.rest.param.ParameterUtil; 036import ca.uhn.fhir.rest.server.IResourceProvider; 037import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 038import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 039import org.apache.commons.lang3.StringUtils; 040import org.hl7.fhir.instance.model.api.IBaseResource; 041import org.hl7.fhir.instance.model.api.IPrimitiveType; 042 043import javax.annotation.Nonnull; 044import java.lang.reflect.Method; 045import java.lang.reflect.Modifier; 046import java.util.Date; 047import java.util.List; 048 049import static org.apache.commons.lang3.StringUtils.isBlank; 050 051public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { 052 053 private final Integer myIdParamIndex; 054 private final RestOperationTypeEnum myResourceOperationType; 055 private final String myResourceName; 056 057 public HistoryMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { 058 super(toReturnType(theMethod, theProvider), theMethod, theContext, theProvider); 059 060 myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext()); 061 062 History historyAnnotation = theMethod.getAnnotation(History.class); 063 Class<? extends IBaseResource> type = historyAnnotation.type(); 064 if (Modifier.isInterface(type.getModifiers())) { 065 if (theProvider instanceof IResourceProvider) { 066 type = ((IResourceProvider) theProvider).getResourceType(); 067 if (myIdParamIndex != null) { 068 myResourceOperationType = RestOperationTypeEnum.HISTORY_INSTANCE; 069 } else { 070 myResourceOperationType = RestOperationTypeEnum.HISTORY_TYPE; 071 } 072 } else { 073 myResourceOperationType = RestOperationTypeEnum.HISTORY_SYSTEM; 074 } 075 } else { 076 if (myIdParamIndex != null) { 077 myResourceOperationType = RestOperationTypeEnum.HISTORY_INSTANCE; 078 } else { 079 myResourceOperationType = RestOperationTypeEnum.HISTORY_TYPE; 080 } 081 } 082 083 if (type != IBaseResource.class && type != IResource.class) { 084 myResourceName = theContext.getResourceType(type); 085 } else { 086 myResourceName = null; 087 } 088 089 } 090 091 @Override 092 protected BundleTypeEnum getResponseBundleType() { 093 return BundleTypeEnum.HISTORY; 094 } 095 096 @Nonnull 097 @Override 098 public RestOperationTypeEnum getRestOperationType() { 099 return myResourceOperationType; 100 } 101 102 @Override 103 public ReturnTypeEnum getReturnType() { 104 return ReturnTypeEnum.BUNDLE; 105 } 106 107 @Override 108 protected boolean isOffsetModeHistory() { 109 return true; 110 } 111 112 // ObjectUtils.equals is replaced by a JDK7 method.. 113 @Override 114 public MethodMatchEnum incomingServerRequestMatchesMethod(RequestDetails theRequest) { 115 if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { 116 return MethodMatchEnum.NONE; 117 } 118 if (theRequest.getResourceName() == null) { 119 if (myResourceOperationType == RestOperationTypeEnum.HISTORY_SYSTEM) { 120 return MethodMatchEnum.EXACT; 121 } else { 122 return MethodMatchEnum.NONE; 123 } 124 } 125 if (!StringUtils.equals(theRequest.getResourceName(), myResourceName)) { 126 return MethodMatchEnum.NONE; 127 } 128 129 boolean haveIdParam = theRequest.getId() != null && !theRequest.getId().isEmpty(); 130 boolean wantIdParam = myIdParamIndex != null; 131 if (haveIdParam != wantIdParam) { 132 return MethodMatchEnum.NONE; 133 } 134 135 if (theRequest.getId() == null) { 136 if (myResourceOperationType != RestOperationTypeEnum.HISTORY_TYPE) { 137 return MethodMatchEnum.NONE; 138 } 139 } else if (theRequest.getId().hasVersionIdPart()) { 140 return MethodMatchEnum.NONE; 141 } 142 143 return MethodMatchEnum.EXACT; 144 } 145 146 147 @Override 148 public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { 149 if (myIdParamIndex != null) { 150 theMethodParams[myIdParamIndex] = theRequest.getId(); 151 } 152 153 Object response = invokeServerMethod(theRequest, theMethodParams); 154 155 final IBundleProvider resources = toResourceList(response); 156 157 /* 158 * We wrap the response so we can verify that it has the ID and version set, 159 * as is the contract for history 160 */ 161 return new IBundleProvider() { 162 163 @Override 164 public String getCurrentPageId() { 165 return resources.getCurrentPageId(); 166 } 167 168 @Override 169 public String getNextPageId() { 170 return resources.getNextPageId(); 171 } 172 173 @Override 174 public String getPreviousPageId() { 175 return resources.getPreviousPageId(); 176 } 177 178 @Override 179 public IPrimitiveType<Date> getPublished() { 180 return resources.getPublished(); 181 } 182 183 @Nonnull 184 @Override 185 public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { 186 List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex); 187 int index = theFromIndex; 188 for (IBaseResource nextResource : retVal) { 189 if (nextResource.getIdElement() == null || isBlank(nextResource.getIdElement().getIdPart())) { 190 throw new InternalErrorException(Msg.code(410) + "Server provided resource at index " + index + " with no ID set (using IResource#setId(IdDt))"); 191 } 192 if (isBlank(nextResource.getIdElement().getVersionIdPart()) && nextResource instanceof IResource) { 193 //TODO: Use of a deprecated method should be resolved. 194 IdDt versionId = ResourceMetadataKeyEnum.VERSION_ID.get((IResource) nextResource); 195 if (versionId == null || versionId.isEmpty()) { 196 throw new InternalErrorException(Msg.code(411) + "Server provided resource at index " + index + " with no Version ID set (using IResource#setId(IdDt))"); 197 } 198 } 199 index++; 200 } 201 return retVal; 202 } 203 204 @Override 205 public String getUuid() { 206 return resources.getUuid(); 207 } 208 209 @Override 210 public Integer preferredPageSize() { 211 return resources.preferredPageSize(); 212 } 213 214 @Override 215 public Integer size() { 216 return resources.size(); 217 } 218 }; 219 } 220 221 private static Class<? extends IBaseResource> toReturnType(Method theMethod, Object theProvider) { 222 if (theProvider instanceof IResourceProvider) { 223 return ((IResourceProvider) theProvider).getResourceType(); 224 } 225 History historyAnnotation = theMethod.getAnnotation(History.class); 226 Class<? extends IBaseResource> type = historyAnnotation.type(); 227 if (type != IBaseResource.class && type != IResource.class) { 228 return type; 229 } 230 return null; 231 } 232 233}