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