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