More work on getting conditional updates working

This commit is contained in:
James Agnew 2015-02-26 17:17:22 -05:00
parent ecd3620e27
commit 0f2eb230e7
18 changed files with 601 additions and 100 deletions

View File

@ -694,6 +694,29 @@ public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
//END SNIPPET: create
//START SNIPPET: createConditional
@Create
public MethodOutcome createPatientConditional(
@ResourceParam Patient thePatient,
@ConditionalOperationParam String theConditionalUrl) {
if (theConditionalUrl != null) {
// We are doing a conditional create
// populate this with the ID of the existing resource which
// matches the conditional URL
return new MethodOutcome();
} else {
// We are doing a normal create
// populate this with the ID of the newly created resource
return new MethodOutcome();
}
}
//END SNIPPET: createConditional
//START SNIPPET: createClient
@Create
public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient);

View File

@ -92,7 +92,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
myMethod = theMethod;
myContext = theContext;
myProvider = theProvider;
myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider);
myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getResourceOperationType());
}
public List<Class<?>> getAllowableParamAnnotations() {

View File

@ -43,6 +43,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
private String myResourceName;
private boolean myBinary;
private Class<? extends IBaseResource> myResourceType;
private Integer myIdParamIndex;
public BaseOutcomeReturningMethodBindingWithResourceParam(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
super(theMethod, theContext, theMethodAnnotation, theProvider);
@ -79,6 +80,8 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
index++;
}
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
if (resourceParameter == null) {
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with @" + ResourceParam.class.getSimpleName());
}
@ -106,12 +109,11 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
}
}
/**
* For subclasses to override
*/
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
// nothing
if (myIdParamIndex != null) {
theParams[myIdParamIndex] = theRequest.getId();
}
}
@Override

View File

@ -20,19 +20,28 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class ConditionalParamBinder implements IParameter {
ConditionalParamBinder() {
super();
private RestfulOperationTypeEnum myOperationType;
ConditionalParamBinder(RestfulOperationTypeEnum theOperationType) {
Validate.notNull(theOperationType, "theOperationType can not be null");
myOperationType = theOperationType;
}
@Override
@ -42,9 +51,34 @@ class ConditionalParamBinder implements IParameter {
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
if (myOperationType == RestfulOperationTypeEnum.CREATE) {
String retVal = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_EXIST);
if (isBlank(retVal)) {
return null;
}
if (retVal.startsWith(theRequest.getFhirServerBase())) {
retVal = retVal.substring(theRequest.getFhirServerBase().length());
}
return retVal;
} else if (myOperationType != RestfulOperationTypeEnum.DELETE && myOperationType != RestfulOperationTypeEnum.UPDATE) {
return null;
}
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
return null;
}
boolean haveParam = false;
for (String next : theRequest.getParameters().keySet()) {
if (!next.startsWith("_")) {
haveParam=true;
break;
}
}
if (!haveParam) {
return null;
}
int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?');
return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex);
}

View File

@ -44,6 +44,7 @@ import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser;
@ -300,7 +301,7 @@ public class MethodUtil {
}
@SuppressWarnings("unchecked")
public static List<IParameter> getResourceParameters(FhirContext theContext, Method theMethod, Object theProvider) {
public static List<IParameter> getResourceParameters(FhirContext theContext, Method theMethod, Object theProvider, RestfulOperationTypeEnum theRestfulOperationTypeEnum) {
List<IParameter> parameters = new ArrayList<IParameter>();
Class<?>[] parameterTypes = theMethod.getParameterTypes();
@ -396,7 +397,7 @@ public class MethodUtil {
} else if (nextAnnotation instanceof TransactionParam) {
param = new TransactionParamBinder(theContext);
} else if (nextAnnotation instanceof ConditionalOperationParam) {
param = new ConditionalParamBinder();
param = new ConditionalParamBinder(theRestfulOperationTypeEnum);
} else {
continue;
}

View File

@ -37,6 +37,7 @@ import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -814,6 +815,10 @@ public class RestfulServer extends HttpServlet {
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
*
* @throws ServletException If the initialization failed. Note that you should consider throwing
* {@link UnavailableException} (which extends {@link ServletException}), as this is a flag
* to the servlet container that the servlet is not usable.
*/
protected void initialize() throws ServletException {
// nothing by default

View File

@ -1,36 +0,0 @@
package ca.uhn.fhir.jpa.entity;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FhirPersister {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("fhir-spring-config.xml");
}
}

View File

@ -50,42 +50,22 @@ import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
public class JpaResourceProvider<T extends IResource> extends BaseJpaProvider implements IResourceProvider {
public abstract class BaseJpaResourceProvider<T extends IResource> extends BaseJpaProvider implements IResourceProvider {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaResourceProvider.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaResourceProvider.class);
private FhirContext myContext;
private IFhirResourceDao<T> myDao;
public JpaResourceProvider() {
public BaseJpaResourceProvider() {
// nothing
}
public JpaResourceProvider(IFhirResourceDao<T> theDao) {
public BaseJpaResourceProvider(IFhirResourceDao<T> theDao) {
myDao = theDao;
}
@Create
public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource) {
startRequest(theRequest);
try {
return myDao.create(theResource);
} finally {
endRequest(theRequest);
}
}
@Delete
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource) {
startRequest(theRequest);
try {
return myDao.delete(theResource);
} finally {
endRequest(theRequest);
}
}
public FhirContext getContext() {
return myContext;
}
@ -158,21 +138,6 @@ public class JpaResourceProvider<T extends IResource> extends BaseJpaProvider im
myDao = theDao;
}
@Update
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId) {
startRequest(theRequest);
try {
theResource.setId(theId);
return myDao.update(theResource);
} catch (ResourceNotFoundException e) {
ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
theResource.setId(theId);
return myDao.create(theResource);
} finally {
endRequest(theRequest);
}
}
@Validate
public MethodOutcome validate(HttpServletRequest theRequest, @ResourceParam T theResource) {
startRequest(theRequest);

View File

@ -0,0 +1,83 @@
package ca.uhn.fhir.jpa.provider;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
public class JpaResourceProviderDstu1<T extends IResource> extends BaseJpaResourceProvider<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaResourceProviderDstu1.class);
public JpaResourceProviderDstu1() {
// nothing
}
public JpaResourceProviderDstu1(IFhirResourceDao<T> theDao) {
super(theDao);
}
@Create
public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource) {
startRequest(theRequest);
try {
return getDao().create(theResource);
} finally {
endRequest(theRequest);
}
}
@Delete
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource) {
startRequest(theRequest);
try {
return getDao().delete(theResource);
} finally {
endRequest(theRequest);
}
}
@Update
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId) {
startRequest(theRequest);
try {
theResource.setId(theId);
return getDao().update(theResource);
} catch (ResourceNotFoundException e) {
ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
theResource.setId(theId);
return getDao().create(theResource);
} finally {
endRequest(theRequest);
}
}
}

View File

@ -0,0 +1,96 @@
package ca.uhn.fhir.jpa.provider;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResourceProvider<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaResourceProviderDstu2.class);
public JpaResourceProviderDstu2() {
// nothing
}
public JpaResourceProviderDstu2(IFhirResourceDao<T> theDao) {
super(theDao);
}
@Create
public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalOperationParam String theConditional) {
startRequest(theRequest);
try {
if (theConditional != null) {
return getDao().create(theResource, theConditional);
} else {
return getDao().create(theResource);
}
} finally {
endRequest(theRequest);
}
}
@Delete
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource, @ConditionalOperationParam String theConditional) {
startRequest(theRequest);
try {
if (theConditional != null) {
return getDao().deleteByUrl(theConditional);
} else {
return getDao().delete(theResource);
}
} finally {
endRequest(theRequest);
}
}
@Update
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId, @ConditionalOperationParam String theConditional) {
startRequest(theRequest);
try {
if (theConditional != null) {
return getDao().update(theResource, theConditional);
} else {
theResource.setId(theId);
return getDao().update(theResource);
}
} catch (ResourceNotFoundException e) {
ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
theResource.setId(theId);
return getDao().create(theResource);
} finally {
endRequest(theRequest);
}
}
}

View File

@ -1,10 +1,9 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.provider.JpaResourceProvider;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Questionnaire;
public class QuestionnaireResourceProvider extends JpaResourceProvider<Questionnaire> {
public class QuestionnaireResourceProvider extends JpaResourceProviderDstu1<Questionnaire> {
@Override
public Class<? extends IResource> getResourceType() {

View File

@ -1,13 +1,29 @@
package ca.uhn.fhir.jpa.provider;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@ -45,6 +61,7 @@ import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
@ -63,6 +80,8 @@ public class ResourceProviderDstu2Test {
private static Server ourServer;
private static IFhirResourceDao<Organization> ourOrganizationDao;
private static DaoConfig ourDaoConfig;
private static CloseableHttpClient ourHttpClient;
private static String ourServerBase;
// private static JpaConformanceProvider ourConfProvider;
@ -92,9 +111,24 @@ public class ResourceProviderDstu2Test {
}
}
public void testTryToCreateResourceWithNumericId() {
@Test
public void testCreateResourceWithNumericId() throws IOException {
String resource = "<Patient xmlns=\"http://hl7.org/fhir\"><id value=\"1777\"/><meta><versionId value=\"1\"/><lastUpdated value=\"2015-02-25T15:47:48Z\"/></meta></Patient>";
HttpPost post = new HttpPost(ourServerBase + "/Patient");
post.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);
try {
assertEquals(201, response.getStatusLine().getStatusCode());
assertThat(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(), startsWith(ourServerBase + "/Patient/"));
assertThat(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(), endsWith("/_history/1"));
assertThat(response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(), not(containsString("1777")));
} finally {
response.close();
}
}
/**
@ -523,6 +557,7 @@ public class ResourceProviderDstu2Test {
public static void afterClass() throws Exception {
ourServer.stop();
ourAppCtx.stop();
ourHttpClient.close();
}
@SuppressWarnings("unchecked")
@ -534,7 +569,7 @@ public class ResourceProviderDstu2Test {
ourFhirCtx = FhirContext.forDstu2();
restServer.setFhirContext(ourFhirCtx);
String serverBase = "http://localhost:" + port + "/fhir/context";
ourServerBase = "http://localhost:" + port + "/fhir/context";
ourAppCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu2.xml", "fhir-jpabase-spring-test-config.xml");
@ -565,9 +600,13 @@ public class ResourceProviderDstu2Test {
ourServer.start();
ourFhirCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
ourClient = ourFhirCtx.newRestfulGenericClient(serverBase);
ourClient = ourFhirCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourHttpClient = builder.build();
}

View File

@ -145,9 +145,9 @@ public class PagingTest {
assertEquals(2, bundle.getEntries().size());
assertEquals("2", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("3", bundle.getEntries().get(1).getId().getIdPart());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2", bundle.getLinkPrevious().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=4&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkNext().getValue());
assertEquals(base + '/' + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=2&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkSelf().getValue());
assertEquals(base + '?' + Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=2&_format=xml", bundle.getLinkPrevious().getValue());
}
}

View File

@ -0,0 +1,200 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class CreateConditionalTest {
private static CloseableHttpClient ourClient;
private static String ourLastConditionalUrl;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateConditionalTest.class);
private static int ourPort;
private static Server ourServer;
private static IdDt ourLastId;
private static IdDt ourLastIdParam;
private static boolean ourLastRequestWasSearch;
@Before
public void before() {
ourLastId = null;
ourLastConditionalUrl = null;
ourLastIdParam = null;
ourLastRequestWasSearch = false;
}
@Test
public void testCreateWithConditionalUrl() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.addHeader(Constants.HEADER_IF_NONE_EXIST, "Patient?identifier=system%7C001");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("content-location").getValue());
assertNull(ourLastId.getValue());
assertNull(ourLastIdParam);
assertEquals("Patient?identifier=system%7C001", ourLastConditionalUrl);
}
@Test
public void testCreateWithoutConditionalUrl() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/2?_format=true&_pretty=true");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("content-location").getValue());
assertEquals("Patient/2", ourLastId.toUnqualified().getValue());
assertEquals("Patient/2", ourLastIdParam.toUnqualified().getValue());
assertNull(ourLastConditionalUrl);
}
@Test
public void testSearchStillWorks() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertTrue(ourLastRequestWasSearch);
assertNull(ourLastId);
assertNull(ourLastIdParam);
assertNull(ourLastConditionalUrl);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class PatientProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Search
public List<IResource> search(@OptionalParam(name="foo") StringDt theString) {
ourLastRequestWasSearch = true;
return new ArrayList<IResource>();
}
@Create()
public MethodOutcome createPatient(@ResourceParam Patient thePatient, @ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {
ourLastConditionalUrl = theConditional;
ourLastId = thePatient.getId();
ourLastIdParam = theIdParam;
return new MethodOutcome(new IdDt("Patient/001/_history/002"));
}
}
}

View File

@ -2,10 +2,13 @@ package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@ -31,9 +34,12 @@ import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.PortUtil;
@ -50,6 +56,7 @@ public class UpdateConditionalTest {
private static Server ourServer;
private static IdDt ourLastId;
private static IdDt ourLastIdParam;
private static boolean ourLastRequestWasSearch;
@ -58,6 +65,7 @@ public class UpdateConditionalTest {
ourLastId = null;
ourLastConditionalUrl = null;
ourLastIdParam = null;
ourLastRequestWasSearch = false;
}
@Test
@ -112,6 +120,29 @@ public class UpdateConditionalTest {
}
@Test
public void testSearchStillWorks() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertTrue(ourLastRequestWasSearch);
assertNull(ourLastId);
assertNull(ourLastIdParam);
assertNull(ourLastConditionalUrl);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
@ -147,6 +178,11 @@ public class UpdateConditionalTest {
return Patient.class;
}
@Search
public List<IResource> search(@OptionalParam(name="foo") StringDt theString) {
ourLastRequestWasSearch = true;
return new ArrayList<IResource>();
}
@Update()
public MethodOutcome updatePatient(@ResourceParam Patient thePatient, @ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {

View File

@ -19,6 +19,7 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.plugin.MojoFailureException;
@ -444,6 +445,12 @@ public abstract class BaseStructureParser {
ctx.put("includes", (theResource.getIncludes()));
ctx.put("esc", new EscapeTool());
String capitalize = WordUtils.capitalize(myVersion);
if ("Dstu".equals(capitalize)) {
capitalize="Dstu1";
}
ctx.put("versionCapitalized", capitalize);
VelocityEngine v = new VelocityEngine();
v.setProperty("resource.loader", "cp");
v.setProperty("cp.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
@ -526,6 +533,13 @@ public abstract class BaseStructureParser {
ctx.put("versionEnumName", determineVersionEnum().name());
ctx.put("esc", new EscapeTool());
String capitalize = WordUtils.capitalize(myVersion);
if ("Dstu".equals(capitalize)) {
capitalize="Dstu1";
}
ctx.put("versionCapitalized", capitalize);
VelocityEngine v = new VelocityEngine();
v.setProperty("resource.loader", "cp");
v.setProperty("cp.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

View File

@ -5,7 +5,7 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.jpa.provider.JpaResourceProvider;
import ca.uhn.fhir.jpa.provider.JpaResourceProvider${versionCapitalized};
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.api.annotation.*;
@ -18,7 +18,7 @@ import ca.uhn.fhir.model.dstu.resource.Binary;
// import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.api.Bundle;
public class ${className}ResourceProvider extends JpaResourceProvider<${className}> {
public class ${className}ResourceProvider extends JpaResourceProvider${versionCapitalized}<${className}> {
@Override
public Class<? extends IResource> getResourceType() {

View File

@ -474,6 +474,35 @@
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<h4>Conditional Creates</h4>
<p>
The FHIR specification also allows "conditional creates". A conditional
create has an additional header called <code>If-None-Exist</code>
which the client will supply on the HTTP request. The client will
populate this header with a search URL such as <code>Patient?identifier=foo</code>.
See the FHIR specification for details on the semantics for correctly
implementing conditional create.
</p>
<p>
When a conditional create is detected (i.e. when the create request contains
a populated <code>If-None-Exist</code> header), if a method parameter annotated
with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.html">@ConditionalOperationParam</a>
is detected, it will be populated with the value of this header.
</p>
<macro name="snippet">
<param name="id" value="createConditional" />
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL and HTTP header to perform a conditional create:
<br />
<code>http://fhir.example.com/Patient<br/>If-None-Exist: Patient?identifier=system%7C0001</code>
</p>
<a name="type_search" />
</section>
@ -535,9 +564,9 @@
<p>
Parameters which take a string as their format should use the
<a href="./apidocs/ca/uhn/fhir/rest/param/StringParam.html">StringParam</a>
type. They may also use normal java Strings, although it is
not possible to use the ":exact" qualifier in that case.
<code><a href="./apidocs/ca/uhn/fhir/rest/param/StringParam.html">StringParam</a></code>
type. They may also use normal java <code>String</code>, although it is
not possible to use the <code>:exact</code> qualifier in that case.
</p>
<macro name="snippet">
@ -971,7 +1000,7 @@
<p>
To accept a composite parameter, use a parameter type which implements the
<a href="./apidocs/ca/uhn/fhir/model/api/IQueryParameterAnd.html">IQueryParameterAnd</a>
interface.
interface (which in turn encapsulates the corresponding IQueryParameterOr types).
</p>
<p>
An example follows which shows a search for Patients by address, where multiple string
@ -992,6 +1021,17 @@
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Note that AND parameters join multiple OR parameters together, but
the inverse is not true. In other words, it is possible in FHIR
to use AND search parameters to specify a search criteria of
<code>(A=1 OR A=2) AND (B=1 OR B=2)</code>
but it is not possible to specify
<code>(A=1 AND B=1) OR (A=2 AND B=2)</code> (aside from
in very specific cases where a composite parameter has been
specifically defined).
</p>
<h4>AND Relationship Query Parameters for Dates</h4>
<p>