Allow server operations to work at the type level
This commit is contained in:
parent
50c43bdb29
commit
b62eb1168c
|
@ -28,5 +28,11 @@ import java.lang.annotation.Target;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.PARAMETER)
|
@Target(ElementType.PARAMETER)
|
||||||
public @interface IdParam {
|
public @interface IdParam {
|
||||||
// just a marker
|
|
||||||
|
/**
|
||||||
|
* For {@link Operation extended operations}, any parameter with this value set to <code>true</code>
|
||||||
|
* (default is false) will also be invoked if the operation is invoked against the resource type.
|
||||||
|
*/
|
||||||
|
boolean optional() default false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.method;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -43,6 +44,7 @@ import ca.uhn.fhir.model.api.Bundle;
|
||||||
import ca.uhn.fhir.model.api.annotation.Description;
|
import ca.uhn.fhir.model.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Operation;
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
|
@ -67,6 +69,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
private final RestOperationTypeEnum myOtherOperatiopnType;
|
private final RestOperationTypeEnum myOtherOperatiopnType;
|
||||||
private List<ReturnType> myReturnParams;
|
private List<ReturnType> myReturnParams;
|
||||||
private final ReturnTypeEnum myReturnType;
|
private final ReturnTypeEnum myReturnType;
|
||||||
|
private boolean myCanOperateAtTypeLevel;
|
||||||
|
|
||||||
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
||||||
OperationParam[] theReturnParams) {
|
OperationParam[] theReturnParams) {
|
||||||
|
@ -74,6 +77,16 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
myIdempotent = theIdempotent;
|
myIdempotent = theIdempotent;
|
||||||
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
|
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
|
||||||
|
if (myIdParamIndex != null) {
|
||||||
|
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
|
||||||
|
if (next instanceof IdParam) {
|
||||||
|
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
myCanOperateAtTypeLevel = true;
|
||||||
|
}
|
||||||
|
|
||||||
Description description = theMethod.getAnnotation(Description.class);
|
Description description = theMethod.getAnnotation(Description.class);
|
||||||
if (description != null) {
|
if (description != null) {
|
||||||
myDescription = description.formalDefinition();
|
myDescription = description.formalDefinition();
|
||||||
|
@ -195,12 +208,22 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean requestHasId = theRequest.getId() != null;
|
if (!myName.equals(theRequest.getOperation())) {
|
||||||
if (requestHasId != (myIdParamIndex != null)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return myName.equals(theRequest.getOperation());
|
boolean requestHasId = theRequest.getId() != null;
|
||||||
|
if (requestHasId) {
|
||||||
|
if (isCanOperateAtInstanceLevel() == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (myCanOperateAtTypeLevel == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
package ca.uhn.fhir.jpa.dao.data;
|
package ca.uhn.fhir.jpa.dao.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%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.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
package ca.uhn.fhir.jpa.dao.data;
|
package ca.uhn.fhir.jpa.dao.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%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.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.SearchResult;
|
import ca.uhn.fhir.jpa.entity.SearchResult;
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
@Operation(name = "$expand", idempotent = true)
|
@Operation(name = "$expand", idempotent = true)
|
||||||
public ValueSet everything(
|
public ValueSet expant(
|
||||||
HttpServletRequest theServletRequest,
|
HttpServletRequest theServletRequest,
|
||||||
|
|
||||||
@IdParam IdDt theId,
|
@IdParam IdDt theId,
|
||||||
|
@ -108,7 +108,7 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst
|
||||||
@OperationParam(name="message", type=StringDt.class),
|
@OperationParam(name="message", type=StringDt.class),
|
||||||
@OperationParam(name="display", type=StringDt.class)
|
@OperationParam(name="display", type=StringDt.class)
|
||||||
})
|
})
|
||||||
public Parameters everything(
|
public Parameters validateCode(
|
||||||
HttpServletRequest theServletRequest,
|
HttpServletRequest theServletRequest,
|
||||||
@IdParam IdDt theId,
|
@IdParam IdDt theId,
|
||||||
@OperationParam(name="identifier") UriDt theValueSetIdentifier,
|
@OperationParam(name="identifier") UriDt theValueSetIdentifier,
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
<class>ca.uhn.fhir.jpa.entity.SubscriptionTable</class>
|
||||||
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
<class>ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource</class>
|
||||||
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
|
<class>ca.uhn.fhir.jpa.entity.TagDefinition</class>
|
||||||
|
<class>ca.uhn.fhir.jpa.entity.Search</class>
|
||||||
|
<class>ca.uhn.fhir.jpa.entity.SearchResult</class>
|
||||||
|
|
||||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||||
<properties>
|
<properties>
|
||||||
|
|
|
@ -274,6 +274,56 @@ public class OperationServerTest {
|
||||||
assertEquals("RET1", resp.getParameter().get(0).getName());
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnInstanceAndType_Instance() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringDt("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE_OR_TYPE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals("123", ourLastId.getIdPart());
|
||||||
|
assertEquals("$OP_INSTANCE_OR_TYPE", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnInstanceAndType_Type() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringDt("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_INSTANCE_OR_TYPE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals(null, ourLastId);
|
||||||
|
assertEquals("$OP_INSTANCE_OR_TYPE", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOperationOnInstance() throws Exception {
|
public void testOperationOnInstance() throws Exception {
|
||||||
Parameters p = new Parameters();
|
Parameters p = new Parameters();
|
||||||
|
@ -296,6 +346,20 @@ public class OperationServerTest {
|
||||||
|
|
||||||
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
assertEquals("RET1", resp.getParameter().get(0).getName());
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Against type should fail
|
||||||
|
*/
|
||||||
|
|
||||||
|
httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_INSTANCE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
ourLog.info(response);
|
||||||
|
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -611,6 +675,25 @@ public class OperationServerTest {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_INSTANCE_OR_TYPE")
|
||||||
|
public Parameters opInstanceOrType(
|
||||||
|
@IdParam(optional=true) IdDt theId,
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_INSTANCE_OR_TYPE";
|
||||||
|
ourLastId = theId;
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringDt("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,13 @@
|
||||||
Refactor JPA $everything operations so that
|
Refactor JPA $everything operations so that
|
||||||
they perform better
|
they perform better
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Server operation methods can now declare the
|
||||||
|
ID optional, via
|
||||||
|
@IdParam(optional=true)
|
||||||
|
meaning that the same operation can also be invoked
|
||||||
|
at the type level.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.2" date="2015-09-18">
|
<release version="1.2" date="2015-09-18">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue