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