mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-03-26 18:08:41 +00:00
Allow plain server @Operation methods to declare a wildcard so that any
opeeration invocations will be direected to them
This commit is contained in:
parent
794d9145e9
commit
b66e01ce65
@ -20,15 +20,14 @@ package ca.uhn.fhir.rest.annotation;
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
|
||||
/**
|
||||
* RESTful method annotation used for a method which provides FHIR "operations".
|
||||
*/
|
||||
@ -36,6 +35,14 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
@Target(value = ElementType.METHOD)
|
||||
public @interface Operation {
|
||||
|
||||
/**
|
||||
* This constant is a special return value for {@link #name()}. If this name is
|
||||
* used, the given operation method will match all operation calls. This is
|
||||
* generally not desirable, but can be useful if you have a server that should
|
||||
* dynamically match any FHIR operations that are requested.
|
||||
*/
|
||||
String NAME_MATCH_ALL = "*";
|
||||
|
||||
/**
|
||||
* The name of the operation, e.g. "<code>$everything</code>"
|
||||
*
|
||||
|
@ -478,8 +478,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
// FIXME: remove
|
||||
ourLog.info("** Sending processing message " + theMessage + " for: " + theMessage.getNewPayload(myCtx));
|
||||
ourLog.trace("Sending resource modified message to processing channel");
|
||||
getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
|
||||
}
|
||||
|
@ -108,10 +108,6 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
|
||||
operation.encoded(thePayloadType);
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
ourLog.info("** This " + this + " Processing delivery message " + theMsg);
|
||||
|
||||
|
||||
ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue());
|
||||
|
||||
try {
|
||||
|
@ -289,7 +289,7 @@ public abstract class BaseJpaTest {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected List<IIdType> toUnqualifiedVersionlessIds(List<IBaseResource> theFound) {
|
||||
protected List<IIdType> toUnqualifiedVersionlessIds(List<? extends IBaseResource> theFound) {
|
||||
List<IIdType> retVal = new ArrayList<IIdType>();
|
||||
for (IBaseResource next : theFound) {
|
||||
retVal.add(next.getIdElement().toUnqualifiedVersionless());
|
||||
|
@ -1613,16 +1613,18 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
||||
obs01.setSubject(new Reference(patientId01));
|
||||
IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
Date between = new Date();
|
||||
Thread.sleep(10);
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
|
||||
Observation obs02 = new Observation();
|
||||
obs02.setEffective(new DateTimeType(new Date()));
|
||||
obs02.setSubject(new Reference(locId01));
|
||||
IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Thread.sleep(10);
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
Date after = new Date();
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
|
||||
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[] { patientId01, locId01, obsId01, obsId02 });
|
||||
|
||||
|
@ -2865,16 +2865,22 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system2").setValue(methodName);
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system3").setValue(methodName);
|
||||
p.addName().setFamily(methodName);
|
||||
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system4").setValue(methodName);
|
||||
p.addName().setFamily(methodName);
|
||||
|
@ -250,6 +250,8 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(5, ourUpdatedObservations);
|
||||
|
||||
ourLog.info("Have observations: {}", toUnqualifiedVersionlessIds(ourUpdatedObservations));
|
||||
|
||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||
Assert.assertFalse(observation1.getId().isEmpty());
|
||||
Assert.assertFalse(observation2.getId().isEmpty());
|
||||
|
@ -349,7 +349,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
/**
|
||||
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
|
||||
*/
|
||||
protected int escapedLength(String theServletPath) {
|
||||
protected static int escapedLength(String theServletPath) {
|
||||
int delta = 0;
|
||||
for (int i = 0; i < theServletPath.length(); i++) {
|
||||
char next = theServletPath.charAt(i);
|
||||
@ -564,6 +564,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
return Collections.unmodifiableList(myInterceptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (or clears) the list of interceptors
|
||||
*
|
||||
* @param theInterceptors The list of interceptors (may be null)
|
||||
*/
|
||||
public void setInterceptors(IServerInterceptor... theInterceptors) {
|
||||
Validate.noNullElements(theInterceptors, "theInterceptors must not contain any null elements");
|
||||
|
||||
myInterceptors.clear();
|
||||
if (theInterceptors != null) {
|
||||
myInterceptors.addAll(Arrays.asList(theInterceptors));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (or clears) the list of interceptors
|
||||
*
|
||||
@ -597,6 +611,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
return myPlainProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the non-resource specific providers which implement method calls on this server.
|
||||
*
|
||||
* @see #setResourceProviders(Collection)
|
||||
*/
|
||||
public void setPlainProviders(Collection<Object> theProviders) {
|
||||
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
||||
|
||||
myPlainProviders.clear();
|
||||
if (theProviders != null) {
|
||||
myPlainProviders.addAll(theProviders);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the non-resource specific providers which implement method calls on this server.
|
||||
*
|
||||
@ -615,7 +643,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
* @param servletPath the servelet path
|
||||
* @return created resource path
|
||||
*/
|
||||
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
|
||||
protected static String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
|
||||
return requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath));
|
||||
}
|
||||
|
||||
@ -630,6 +658,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
return myResourceProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resource providers for this server
|
||||
*/
|
||||
public void setResourceProviders(Collection<IResourceProvider> theProviders) {
|
||||
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
||||
|
||||
myResourceProviders.clear();
|
||||
if (theProviders != null) {
|
||||
myResourceProviders.addAll(theProviders);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resource providers for this server
|
||||
*/
|
||||
@ -1521,34 +1561,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (or clears) the list of interceptors
|
||||
*
|
||||
* @param theInterceptors The list of interceptors (may be null)
|
||||
*/
|
||||
public void setInterceptors(IServerInterceptor... theInterceptors) {
|
||||
Validate.noNullElements(theInterceptors, "theInterceptors must not contain any null elements");
|
||||
|
||||
myInterceptors.clear();
|
||||
if (theInterceptors != null) {
|
||||
myInterceptors.addAll(Arrays.asList(theInterceptors));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the non-resource specific providers which implement method calls on this server.
|
||||
*
|
||||
* @see #setResourceProviders(Collection)
|
||||
*/
|
||||
public void setPlainProviders(Collection<Object> theProviders) {
|
||||
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
||||
|
||||
myPlainProviders.clear();
|
||||
if (theProviders != null) {
|
||||
myPlainProviders.addAll(theProviders);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the non-resource specific providers which implement method calls on this server
|
||||
*
|
||||
@ -1563,18 +1575,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resource providers for this server
|
||||
*/
|
||||
public void setResourceProviders(Collection<IResourceProvider> theProviders) {
|
||||
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
||||
|
||||
myResourceProviders.clear();
|
||||
if (theProviders != null) {
|
||||
myResourceProviders.addAll(theProviders);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If provided (default is <code>null</code>), the tenant identification
|
||||
* strategy provides a mechanism for a multitenant server to identify which tenant
|
||||
@ -1585,7 +1585,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
}
|
||||
|
||||
protected void throwUnknownFhirOperationException(RequestDetails requestDetails, String requestPath, RequestTypeEnum theRequestType) {
|
||||
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", theRequestType.name(), requestPath, requestDetails.getParameters().keySet()));
|
||||
FhirContext fhirContext = myFhirContext;
|
||||
throwUnknownFhirOperationException(requestDetails, requestPath, theRequestType, fhirContext);
|
||||
}
|
||||
|
||||
protected void throwUnknownResourceTypeException(String theResourceName) {
|
||||
@ -1647,6 +1648,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||
theResponse.getWriter().write(theException.getMessage());
|
||||
}
|
||||
|
||||
public static void throwUnknownFhirOperationException(RequestDetails requestDetails, String requestPath, RequestTypeEnum theRequestType, FhirContext theFhirContext) {
|
||||
throw new InvalidRequestException(theFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", theRequestType.name(), requestPath, requestDetails.getParameters().keySet()));
|
||||
}
|
||||
|
||||
private static boolean partIsOperation(String nextString) {
|
||||
return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@ -223,6 +224,7 @@ public abstract class BaseMethodBinding<T> {
|
||||
*/
|
||||
public abstract String getResourceName();
|
||||
|
||||
@Nonnull
|
||||
public abstract RestOperationTypeEnum getRestOperationType();
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,8 @@ import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
public ConformanceMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
@ -86,6 +88,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.METADATA;
|
||||
|
@ -36,6 +36,8 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
|
||||
|
||||
public CreateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
@ -47,6 +49,7 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.CREATE;
|
||||
|
@ -30,12 +30,15 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceIdButNoResourceBody {
|
||||
|
||||
public DeleteMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
super(theMethod, theContext, theProvider, Delete.class, theMethod.getAnnotation(Delete.class).type());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.DELETE;
|
||||
|
@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Method;
|
||||
@ -49,6 +50,7 @@ public class GraphQLMethodBinding extends BaseMethodBinding<String> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.GRAPHQL_REQUEST;
|
||||
|
@ -39,6 +39,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Date;
|
||||
@ -91,6 +92,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return BundleTypeEnum.HISTORY;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return myResourceOperationType;
|
||||
|
@ -193,7 +193,8 @@ public class MethodUtil {
|
||||
b.append(" or String or byte[]");
|
||||
throw new ConfigurationException(b.toString());
|
||||
}
|
||||
param = new ResourceParameter((Class<? extends IBaseResource>) parameterType, theProvider, mode);
|
||||
boolean methodIsOperation = theMethod.getAnnotation(Operation.class) != null;
|
||||
param = new ResourceParameter((Class<? extends IBaseResource>) parameterType, theProvider, mode, methodIsOperation);
|
||||
} else if (nextAnnotation instanceof IdParam) {
|
||||
param = new NullParameter();
|
||||
} else if (nextAnnotation instanceof ServerBase) {
|
||||
|
@ -19,46 +19,54 @@ package ca.uhn.fhir.rest.server.method;
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.*;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
public static final String WILDCARD_NAME = "$" + Operation.NAME_MATCH_ALL;
|
||||
private final boolean myIdempotent;
|
||||
private final Integer myIdParamIndex;
|
||||
private final String myName;
|
||||
private final RestOperationTypeEnum myOtherOperatiopnType;
|
||||
private final ReturnTypeEnum myReturnType;
|
||||
private BundleTypeEnum myBundleType;
|
||||
private boolean myCanOperateAtInstanceLevel;
|
||||
private boolean myCanOperateAtServerLevel;
|
||||
private boolean myCanOperateAtTypeLevel;
|
||||
private String myDescription;
|
||||
private final boolean myIdempotent;
|
||||
private final Integer myIdParamIndex;
|
||||
private final String myName;
|
||||
private final RestOperationTypeEnum myOtherOperatiopnType;
|
||||
private List<ReturnType> myReturnParams;
|
||||
private final ReturnTypeEnum myReturnType;
|
||||
|
||||
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
||||
@ -161,6 +169,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return myDescription;
|
||||
}
|
||||
|
||||
public void setDescription(String theDescription) {
|
||||
myDescription = theDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the operation, starting with "$"
|
||||
*/
|
||||
@ -173,6 +185,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return myBundleType;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return myOtherOperatiopnType;
|
||||
@ -189,15 +202,19 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if (!myName.equals(theRequest.getOperation())) {
|
||||
if (!myName.equals(WILDCARD_NAME)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (getResourceName() == null) {
|
||||
if (isNotBlank(theRequest.getResourceName())) {
|
||||
return false;
|
||||
}
|
||||
} else if (!getResourceName().equals(theRequest.getResourceName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!myName.equals(theRequest.getOperation())) {
|
||||
if (getResourceName() != null && !getResourceName().equals(theRequest.getResourceName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -221,7 +238,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
|
||||
RestOperationTypeEnum retVal = super.getRestOperationType(theRequestDetails);
|
||||
@ -304,11 +320,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
theDetails.setResource((IBaseResource) theRequestDetails.getUserData().get(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY));
|
||||
}
|
||||
|
||||
public void setDescription(String theDescription) {
|
||||
myDescription = theDescription;
|
||||
}
|
||||
|
||||
|
||||
public static class ReturnType {
|
||||
private int myMax;
|
||||
private int myMin;
|
||||
@ -322,30 +333,30 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return myMax;
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return myMin;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
public void setMax(int theMax) {
|
||||
myMax = theMax;
|
||||
}
|
||||
|
||||
public int getMin() {
|
||||
return myMin;
|
||||
}
|
||||
|
||||
public void setMin(int theMin) {
|
||||
myMin = theMin;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
public void setName(String theName) {
|
||||
myName = theName;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
public void setType(String theType) {
|
||||
myType = theType;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@ -166,6 +167,7 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.GET_PAGE;
|
||||
|
@ -38,6 +38,8 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Base class for an operation that has a resource type but not a resource body in the
|
||||
* request body
|
||||
@ -86,6 +88,7 @@ public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithRes
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.PATCH;
|
||||
|
@ -44,6 +44,8 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||
import ca.uhn.fhir.util.DateUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
|
||||
|
||||
@ -91,6 +93,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
|
||||
|
@ -38,6 +38,7 @@ import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
@ -52,15 +53,17 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class ResourceParameter implements IParameter {
|
||||
|
||||
private final boolean myMethodIsOperation;
|
||||
private Mode myMode;
|
||||
private Class<? extends IBaseResource> myResourceType;
|
||||
|
||||
public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode) {
|
||||
public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode, boolean theMethodIsOperation) {
|
||||
Validate.notNull(theParameterType, "theParameterType can not be null");
|
||||
Validate.notNull(theMode, "theMode can not be null");
|
||||
|
||||
myResourceType = theParameterType;
|
||||
myMode = theMode;
|
||||
myMethodIsOperation = theMethodIsOperation;
|
||||
|
||||
Class<? extends IBaseResource> providerResourceType = null;
|
||||
if (theProvider instanceof IResourceProvider) {
|
||||
@ -103,11 +106,20 @@ public class ResourceParameter implements IParameter {
|
||||
return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
|
||||
case RESOURCE:
|
||||
default:
|
||||
return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType);
|
||||
Class<? extends IBaseResource> resourceTypeToParse = myResourceType;
|
||||
if (myMethodIsOperation) {
|
||||
// Operations typically have a Parameters resource as the body
|
||||
resourceTypeToParse = null;
|
||||
}
|
||||
return parseResourceFromRequest(theRequest, theMethodBinding, resourceTypeToParse);
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE
|
||||
}
|
||||
|
||||
public static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
|
||||
Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
|
||||
return requestReader;
|
||||
@ -126,7 +138,7 @@ public class ResourceParameter implements IParameter {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
|
||||
public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
|
||||
FhirContext ctx = theRequest.getServer().getFhirContext();
|
||||
|
||||
final Charset charset = determineRequestCharset(theRequest);
|
||||
@ -139,7 +151,6 @@ public class ResourceParameter implements IParameter {
|
||||
String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
||||
if (ctValue != null) {
|
||||
if (ctValue.startsWith("application/x-www-form-urlencoded")) {
|
||||
//FIXME potential null access theMethodBinding
|
||||
String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
|
||||
throw new InvalidRequestException(msg);
|
||||
}
|
||||
@ -155,6 +166,9 @@ public class ResourceParameter implements IParameter {
|
||||
// This shouldn't happen since we're reading from a byte array..
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
if (isBlank(body)) {
|
||||
return null;
|
||||
}
|
||||
encoding = EncodingEnum.detectEncodingNoDefault(body);
|
||||
if (encoding == null) {
|
||||
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
|
||||
@ -187,7 +201,7 @@ public class ResourceParameter implements IParameter {
|
||||
public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
|
||||
IBaseResource retVal = null;
|
||||
|
||||
if (IBaseBinary.class.isAssignableFrom(theResourceType)) {
|
||||
if (theResourceType != null && IBaseBinary.class.isAssignableFrom(theResourceType)) {
|
||||
String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
||||
if (EncodingEnum.forContentTypeStrict(ct) == null) {
|
||||
FhirContext ctx = theRequest.getServer().getFhirContext();
|
||||
@ -216,8 +230,4 @@ public class ResourceParameter implements IParameter {
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ import ca.uhn.fhir.rest.param.QualifierDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
||||
|
||||
@ -108,6 +110,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
return myDescription;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.SEARCH_TYPE;
|
||||
|
@ -44,6 +44,8 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.method.TransactionParameter.ParamStyle;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class TransactionMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
private int myTransactionParamIndex;
|
||||
@ -73,6 +75,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.TRANSACTION;
|
||||
|
@ -39,6 +39,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
|
||||
|
||||
public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
@ -98,6 +100,7 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.UPDATE;
|
||||
|
@ -0,0 +1,276 @@
|
||||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
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.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class OperationGenericServerR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationGenericServerR4Test.class);
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx;
|
||||
private static IdType ourLastId;
|
||||
private static String ourLastMethod;
|
||||
private static StringType ourLastParam1;
|
||||
private static Patient ourLastParam2;
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static Parameters ourLastResourceParam;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastParam1 = null;
|
||||
ourLastParam2 = null;
|
||||
ourLastId = null;
|
||||
ourLastMethod = "";
|
||||
ourLastResourceParam = null;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOperationOnInstance() throws Exception {
|
||||
Parameters p = new Parameters();
|
||||
p.addParameter().setName("PARAM1").setValue(new StringType("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");
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
try {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
status.getEntity().getContent().close();
|
||||
|
||||
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||
assertEquals(true, ourLastParam2.getActive());
|
||||
assertEquals("123", ourLastId.getIdPart());
|
||||
assertEquals("$OP_INSTANCE", ourLastMethod);
|
||||
assertEquals("PARAM1", ourLastResourceParam.getParameterFirstRep().getName());
|
||||
|
||||
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||
} finally {
|
||||
status.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOperationOnServer() throws Exception {
|
||||
Parameters p = new Parameters();
|
||||
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
|
||||
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER");
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
try {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
|
||||
assertEquals("PARAM1", ourLastResourceParam.getParameterFirstRep().getName());
|
||||
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||
assertEquals(true, ourLastParam2.getActive());
|
||||
assertEquals("$OP_SERVER", ourLastMethod);
|
||||
|
||||
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||
} finally {
|
||||
status.getEntity().getContent().close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOperationOnType() throws Exception {
|
||||
Parameters p = new Parameters();
|
||||
p.addParameter().setName("PARAM1").setValue(new StringType("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_TYPE");
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||
try {
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(response);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
status.getEntity().getContent().close();
|
||||
|
||||
assertEquals("PARAM1", ourLastResourceParam.getParameterFirstRep().getName());
|
||||
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||
assertEquals(true, ourLastParam2.getActive());
|
||||
assertEquals("$OP_TYPE", ourLastMethod);
|
||||
|
||||
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||
} finally {
|
||||
status.getEntity().getContent().close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOperationWithGetUsingParams() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE?PARAM1=PARAM1val");
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(response);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
status.getEntity().getContent().close();
|
||||
|
||||
assertNull(ourLastResourceParam);
|
||||
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||
|
||||
assertNull(ourLastParam2);
|
||||
assertEquals("$OP_TYPE", ourLastMethod);
|
||||
|
||||
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||
} finally {
|
||||
status.getEntity().getContent().close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Operation(name = Operation.NAME_MATCH_ALL)
|
||||
public Parameters opInstance(
|
||||
@ResourceParam() IBaseResource theResourceParam,
|
||||
@IdParam IdType theId,
|
||||
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||
@OperationParam(name = "PARAM2") Patient theParam2
|
||||
) {
|
||||
|
||||
ourLastMethod = "$OP_INSTANCE";
|
||||
ourLastId = theId;
|
||||
ourLastParam1 = theParam1;
|
||||
ourLastParam2 = theParam2;
|
||||
ourLastResourceParam = (Parameters) theResourceParam;
|
||||
|
||||
Parameters retVal = new Parameters();
|
||||
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Operation(name = Operation.NAME_MATCH_ALL, idempotent = true)
|
||||
public Parameters opType(
|
||||
@ResourceParam() IBaseResource theResourceParam,
|
||||
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||
@OperationParam(name = "PARAM2") Patient theParam2,
|
||||
@OperationParam(name = "PARAM3", min = 2, max = 5) List<StringType> theParam3,
|
||||
@OperationParam(name = "PARAM4", min = 1) List<StringType> theParam4
|
||||
) {
|
||||
|
||||
ourLastMethod = "$OP_TYPE";
|
||||
ourLastParam1 = theParam1;
|
||||
ourLastParam2 = theParam2;
|
||||
ourLastResourceParam = (Parameters) theResourceParam;
|
||||
|
||||
Parameters retVal = new Parameters();
|
||||
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class PlainProvider {
|
||||
|
||||
@Operation(name = Operation.NAME_MATCH_ALL)
|
||||
public Parameters opServer(
|
||||
@ResourceParam() IBaseResource theResourceParam,
|
||||
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||
@OperationParam(name = "PARAM2") Patient theParam2
|
||||
) {
|
||||
|
||||
ourLastMethod = "$OP_SERVER";
|
||||
ourLastParam1 = theParam1;
|
||||
ourLastParam2 = theParam2;
|
||||
ourLastResourceParam = (Parameters) theResourceParam;
|
||||
|
||||
Parameters retVal = new Parameters();
|
||||
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourCtx = FhirContext.forR4();
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
|
||||
|
||||
servlet.setFhirContext(ourCtx);
|
||||
servlet.setResourceProviders(new PatientProvider());
|
||||
servlet.setPlainProviders(new PlainProvider());
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -121,6 +121,18 @@
|
||||
sometimes even though the index value hadn't changed. This issue did not cause incorrect search
|
||||
results but had an effect on write performance. This has been corrected.
|
||||
</action>
|
||||
<action type="add">
|
||||
The @Operation annotation used to declare operations on the Plain Server now
|
||||
has a wildcard constant which may be used for the operation name. This allows
|
||||
you to create a server that supports operations that are not known to the
|
||||
server when it starts up. This is generally not advisable but can be useful
|
||||
for some circumstances.
|
||||
</action>
|
||||
<action type="add">
|
||||
When using an @Operation method in the Plain Server, it is now possible
|
||||
to use a parameter annotated with @ResourceParam to receive the Parameters
|
||||
(or other) resource supplied by the client as the request body.
|
||||
</action>
|
||||
</release>
|
||||
|
||||
<release version="3.5.0" date="2018-09-17">
|
||||
@ -1316,7 +1328,8 @@ ALTER TABLE hfj_res_ver ALTER COLUMN res_text DROP NOT NULL;</pre>
|
||||
Michael Lawley for the pull request!
|
||||
</action>
|
||||
<action type="add">
|
||||
Add <![CDATA[<code>Prefer</code> and <code>Cache-Control</code>]]> to the list of headers which are declared as
|
||||
Add <![CDATA[<code>Prefer</code> and <code>Cache-Control</code>]]> to the list of headers which are declared
|
||||
as
|
||||
being acceptable for CORS requests in CorsInterceptor, CLI, and JPA Example.
|
||||
Thanks to Patrick Werner for the pull request!
|
||||
</action>
|
||||
@ -1826,7 +1839,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
optimize something that did not need optimizing!
|
||||
</action>
|
||||
<action type="add">
|
||||
A new config property has been added to the JPA seerver DaoConfig called "setAutoCreatePlaceholderReferenceTargets".
|
||||
A new config property has been added to the JPA seerver DaoConfig called
|
||||
"setAutoCreatePlaceholderReferenceTargets".
|
||||
This property causes references to unknown resources in created/updated resources to have a placeholder
|
||||
target resource automatically created.
|
||||
</action>
|
||||
@ -2126,7 +2140,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
</action>
|
||||
<action type="add">
|
||||
Add a utility method to JPA server:
|
||||
<![CDATA[<code>IFhirResourceDao#removeTag(IIdType, TagTypeEnum, String, String)</code>]]>. This allows client code to remove tags
|
||||
<![CDATA[<code>IFhirResourceDao#removeTag(IIdType, TagTypeEnum, String, String)</code>]]>. This allows
|
||||
client code to remove tags
|
||||
from a resource without having a servlet request object in context.
|
||||
</action>
|
||||
<action type="fix">
|
||||
@ -2520,8 +2535,10 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
<![CDATA[<code>IHttpRequest</code>]]> class: "bufferEntitity" should be "bufferEntity".
|
||||
</action>
|
||||
<action type="add">
|
||||
ErrorHandler is now called (resulting in a warning by default, but can also be an exception) when arsing JSON if
|
||||
the resource ID is not a JSON string, or an object is found where an array is expected (e.g. repeating field). Thanks
|
||||
ErrorHandler is now called (resulting in a warning by default, but can also be an exception) when arsing
|
||||
JSON if
|
||||
the resource ID is not a JSON string, or an object is found where an array is expected (e.g. repeating
|
||||
field). Thanks
|
||||
to Jenni Syed of Cerner for providing a test case!
|
||||
</action>
|
||||
<action type="fix">
|
||||
@ -2644,8 +2661,10 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
]]>
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix a fairly significant issue in JPA Server when using the <![CDATA[<code>DatabaseBackedPagingProvider</code>]]>: When paging over the results
|
||||
of a search / $everything operation, under certain circumstances resources may be missing from the last page of results
|
||||
Fix a fairly significant issue in JPA Server when using the
|
||||
<![CDATA[<code>DatabaseBackedPagingProvider</code>]]>: When paging over the results
|
||||
of a search / $everything operation, under certain circumstances resources may be missing from the last page
|
||||
of results
|
||||
that is returned. Thanks to David Hay for reporting!
|
||||
</action>
|
||||
<action type="add">
|
||||
@ -2800,7 +2819,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Kevin Tallevi for finding this!
|
||||
</action>
|
||||
<action type="fix" issue="411">
|
||||
Fix #411 - Searching by <![CDATA[<code>POST [base]/_search</code>]]> with urlencoded parameters doesn't work correctly if
|
||||
Fix #411 - Searching by <![CDATA[<code>POST [base]/_search</code>]]> with urlencoded parameters doesn't work
|
||||
correctly if
|
||||
interceptors are accessing the parameters and there is are also
|
||||
parameters on the URL. Thanks to Jim Steel for reporting!
|
||||
</action>
|
||||
@ -2913,7 +2933,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
</action>
|
||||
<action type="add">
|
||||
Both client and server now support the new Content Types decided in
|
||||
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=10199">FHIR #10199</a>]]>.
|
||||
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=10199">FHIR #10199</a>]]>
|
||||
.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
This means that the server now supports
|
||||
<![CDATA[<code>application/fhir+xml</code> and <code>application/fhir+json</code>]]>
|
||||
@ -3390,7 +3411,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
reporting!
|
||||
</action>
|
||||
<action type="fix" issue="371">
|
||||
Update STU3 client and server to use the new sort parameter style (param1,-param2,param). Thanks to GitHub user @euz1e4r for
|
||||
Update STU3 client and server to use the new sort parameter style (param1,-param2,param). Thanks to GitHub
|
||||
user @euz1e4r for
|
||||
reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
@ -3733,7 +3755,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
REST Server responded to HTTP OPTIONS requests with
|
||||
any URI as being a request for the server's
|
||||
Conformance statement. This is incorrect, as only
|
||||
a request for <![CDATA[<code>OPTIONS [base url]</code>]]> should be treated as such. Thanks to Michael Lawley for reporting!
|
||||
a request for <![CDATA[<code>OPTIONS [base url]</code>]]> should be treated as such. Thanks to Michael
|
||||
Lawley for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
REST annotation style client was not able to handle extended operations
|
||||
@ -4195,7 +4218,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
</action>
|
||||
<action type="fix">
|
||||
In server, if a client request is received and it has an Accept header indicating
|
||||
that it supports both XML and JSON with equal weight, the server's default is used instead of the first entry in the list.
|
||||
that it supports both XML and JSON with equal weight, the server's default is used instead of the first
|
||||
entry in the list.
|
||||
</action>
|
||||
<action type="add">
|
||||
JPA server now supports searching with sort by token, quantity,
|
||||
@ -4252,7 +4276,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
to Alexander Kley for the fix!
|
||||
</action>
|
||||
<action type="add">
|
||||
JPA server now supports $everything on Patient and Encounter types (patient and encounter instance was already supported)
|
||||
JPA server now supports $everything on Patient and Encounter types (patient and encounter instance was
|
||||
already supported)
|
||||
</action>
|
||||
<action type="add">
|
||||
Generic client operation invocations now
|
||||
@ -4399,7 +4424,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
</action>
|
||||
<action type="fix" issue="198">
|
||||
JPA server sorting often returned unexpected orders when multiple
|
||||
indexes of the same type were found on the same resource (e.g. multiple string indexed fields). Thanks to Travis Cummings for reporting!
|
||||
indexes of the same type were found on the same resource (e.g. multiple string indexed fields). Thanks to
|
||||
Travis Cummings for reporting!
|
||||
</action>
|
||||
<action type="add">
|
||||
Add another method to IServerInterceptor which converts an exception generated on the server
|
||||
@ -4508,10 +4534,13 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
</action>
|
||||
<action type="fix">
|
||||
JPA server did not correctly index search parameters
|
||||
of type "URI". Thanks to David Hay for reporting! Note that if you are using the JPA server, this change means that
|
||||
there are two new tables added to the database schema. Updating existing resources in the database may fail unless you
|
||||
of type "URI". Thanks to David Hay for reporting! Note that if you are using the JPA server, this change
|
||||
means that
|
||||
there are two new tables added to the database schema. Updating existing resources in the database may fail
|
||||
unless you
|
||||
set default values for the resource
|
||||
table by issuing a SQL command similar to the following (false may be 0 or something else, depending on the database platform in use)
|
||||
table by issuing a SQL command similar to the following (false may be 0 or something else, depending on the
|
||||
database platform in use)
|
||||
<![CDATA[<br/><code>update hfj_resource set sp_coords_present = false;<br/>
|
||||
update hfj_resource set sp_uri_present = false;</code>]]>
|
||||
</action>
|
||||
@ -4556,7 +4585,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
in history
|
||||
</action>
|
||||
<action type="fix" issue="222">
|
||||
JPA server returned deleted resources in search results when using the _tag, _id, _profile, or _security search parameters
|
||||
JPA server returned deleted resources in search results when using the _tag, _id, _profile, or _security
|
||||
search parameters
|
||||
</action>
|
||||
<action type="fix" issue="223">
|
||||
Fix issue with build on Windows. Thanks to Bryce van Dyk for the pull request!
|
||||
@ -4576,7 +4606,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Claude Nanjo for finding this.
|
||||
</action>
|
||||
<action type="fix" issue="164">
|
||||
Correct performance issue with :missing=true search requests where the parameter is a resource link. Thanks to wanghaisheng for all his help in testing this.
|
||||
Correct performance issue with :missing=true search requests where the parameter is a resource link. Thanks
|
||||
to wanghaisheng for all his help in testing this.
|
||||
</action>
|
||||
<action type="fix" issue="149">
|
||||
The self link in the Bundle returned by searches on the server does not respect the
|
||||
@ -4593,7 +4624,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Peter Girard for reporting!
|
||||
</action>
|
||||
<action type="add" issue="170">
|
||||
Add better addXXX() methods to structures, which take the datatype being added as a parameter. Thanks to Claude Nanjo for the
|
||||
Add better addXXX() methods to structures, which take the datatype being added as a parameter. Thanks to
|
||||
Claude Nanjo for the
|
||||
suggestion!
|
||||
</action>
|
||||
<action type="add" issue="152">
|
||||
@ -4663,7 +4695,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
q values specifying order of preference. Previously the q value was ignored.
|
||||
</action>
|
||||
<action type="add">
|
||||
Server in DSTU2 mode now indicates that whether it has support for Transaction operation or not. Thanks to Kevin Paschke for pointing out that this wasn't working!
|
||||
Server in DSTU2 mode now indicates that whether it has support for Transaction operation or not. Thanks to
|
||||
Kevin Paschke for pointing out that this wasn't working!
|
||||
</action>
|
||||
<action type="add" issue="166">
|
||||
Questionnaire.title now gets correctly indexed in JPA server (it has no path, so it is a special case)
|
||||
@ -4749,7 +4782,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
McKenzie for reporting!
|
||||
</action>
|
||||
<action type="fix" issue="128">
|
||||
Fix regression in 0.9 - Server responds with an HTTP 500 and a NullPointerException instead of an HTTP 400 and a useful error message if the client requests an unknown resource type
|
||||
Fix regression in 0.9 - Server responds with an HTTP 500 and a NullPointerException instead of an HTTP 400
|
||||
and a useful error message if the client requests an unknown resource type
|
||||
</action>
|
||||
<action type="add">
|
||||
Add support for
|
||||
@ -4921,7 +4955,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
the patch!
|
||||
</action>
|
||||
<action type="fix">
|
||||
Transaction server operations incorrectly used the "Accept" header instead of the "Content-Type" header to determine the
|
||||
Transaction server operations incorrectly used the "Accept" header instead of the "Content-Type" header to
|
||||
determine the
|
||||
POST request encoding. Thanks to Rene Spronk for providing a test case!
|
||||
</action>
|
||||
</release>
|
||||
@ -5037,7 +5072,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server requests for Binary resources where the client has explicitly requested XML or JSON responses
|
||||
(either with a <![CDATA[<code>_format</code>]]> URL parameter, or an <![CDATA[<code>Accept</code>]]> request header)
|
||||
(either with a <![CDATA[<code>_format</code>]]> URL parameter, or an <![CDATA[<code>Accept</code>]]> request
|
||||
header)
|
||||
will be responded to using the Binary FHIR resource type instead of as Binary blobs. This is
|
||||
in accordance with the recommended behaviour in the FHIR specification.
|
||||
</action>
|
||||
@ -5075,7 +5111,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
to baopingle for reporting and providing a test case!
|
||||
</action>
|
||||
<action type="add">
|
||||
Sorting is now supported in the Web Testing UI (previously a button existed for sorting, but it didn't do anything)
|
||||
Sorting is now supported in the Web Testing UI (previously a button existed for sorting, but it didn't do
|
||||
anything)
|
||||
</action>
|
||||
<action type="add" issue="111">
|
||||
Server will no longer include stack traces in the OperationOutcome returned to the client
|
||||
@ -5349,7 +5386,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
for reporting this!
|
||||
</action>
|
||||
<action type="fix">
|
||||
XHTML (in narratives) containing escapable characters (e.g. < or ") will now always have those characters
|
||||
XHTML (in narratives) containing escapable characters (e.g. < or ") will now always have those
|
||||
characters
|
||||
escaped properly in encoded messages.
|
||||
</action>
|
||||
<action type="fix">
|
||||
@ -5399,7 +5437,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Thanks to Bill de Beaubien for reporting!
|
||||
</action>
|
||||
<action type="update">
|
||||
Documentation on contained resources contained a typo and did not actually produce contained resources. Thanks
|
||||
Documentation on contained resources contained a typo and did not actually produce contained resources.
|
||||
Thanks
|
||||
to David Hay of Orion Health for reporting!
|
||||
</action>
|
||||
<action type="add" issue="31" dev="preston">
|
||||
@ -5418,7 +5457,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Petro Mykhailysyn for the pull request!
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.6" date="2014-09-08" description="This release brings a number of new features and bug fixes!">
|
||||
<release version="0.6" date="2014-09-08"
|
||||
description="This release brings a number of new features and bug fixes!">
|
||||
<!--
|
||||
<action type="add">
|
||||
Allow generic client ... OAUTH
|
||||
@ -5517,13 +5557,15 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Rename NotImpementedException to NotImplementedException (to correct typo)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with 4xx/5xx status)
|
||||
Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with
|
||||
4xx/5xx status)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fix performance issue in date/time datatypes where pattern matchers were not static
|
||||
</action>
|
||||
<action type="fix">
|
||||
Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid, but
|
||||
Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid,
|
||||
but
|
||||
previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting!
|
||||
</action>
|
||||
<action type="fix">
|
||||
@ -5596,7 +5638,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
for configurable logging, capturing requests and responses, and HTTP basic auth.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" instead
|
||||
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir"
|
||||
instead
|
||||
of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one!
|
||||
</action>
|
||||
<action type="add">
|
||||
@ -5675,7 +5718,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Support for Query resources fixed (in parser)
|
||||
</action>
|
||||
<action type="fix">
|
||||
Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a resource)
|
||||
Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a
|
||||
resource)
|
||||
now parse and encode correctly, meaning that all contained resources are placed in the "contained" element
|
||||
of the root resource, and the parser looks in the root resource for all container levels when stitching
|
||||
contained resources back together.
|
||||
@ -5708,7 +5752,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||
Support added for deleted-entry by/name, by/email, and comment from Tombstones spec
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.3" date="2014-05-12" description="This release corrects lots of bugs and introduces the fluent client mode">
|
||||
<release version="0.3" date="2014-05-12"
|
||||
description="This release corrects lots of bugs and introduces the fluent client mode">
|
||||
</release>
|
||||
<release version="0.2" date="2014-04-23">
|
||||
</release>
|
||||
|
Loading…
x
Reference in New Issue
Block a user