Detect invalid read method

This commit is contained in:
James Agnew 2014-08-25 17:07:02 -04:00
parent d01f43e4b3
commit 2cad32aa08
11 changed files with 150 additions and 57 deletions

View File

@ -103,6 +103,10 @@
<action type="fix"> <action type="fix">
Fix performance issue in date/time datatypes where pattern matchers were not static Fix performance issue in date/time datatypes where pattern matchers were not static
</action> </action>
<action type="fix">
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>
</release> </release>
<release version="0.5" date="2014-Jul-30"> <release version="0.5" date="2014-Jul-30">
<action type="add"> <action type="add">

View File

@ -95,6 +95,11 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
myParameters = MethodUtil.getResourceParameters(theMethod); myParameters = MethodUtil.getResourceParameters(theMethod);
} }
public List<Class<?>> getAllowableParamAnnotations() {
return null;
}
public Set<String> getIncludes() { public Set<String> getIncludes() {
Set<String> retVal = new TreeSet<String>(); Set<String> retVal = new TreeSet<String>();
for (IParameter next : myParameters) { for (IParameter next : myParameters) {

View File

@ -23,6 +23,9 @@ package ca.uhn.fhir.rest.method;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -39,6 +42,7 @@ import ca.uhn.fhir.model.dstu.resource.Binary;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
@ -52,8 +56,8 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
private Integer myIdIndex; private Integer myIdIndex;
private Integer myVersionIdIndex;
private boolean mySupportsVersion; private boolean mySupportsVersion;
private Integer myVersionIdIndex;
public ReadMethodBinding(Class<? extends IResource> theAnnotatedResourceType, Method theMethod, FhirContext theContext, Object theProvider) { public ReadMethodBinding(Class<? extends IResource> theAnnotatedResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
super(theAnnotatedResourceType, theMethod, theContext, theProvider); super(theAnnotatedResourceType, theMethod, theContext, theProvider);
@ -77,8 +81,26 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
} }
public boolean isVread() { @Override
return mySupportsVersion || myVersionIdIndex != null; public List<Class<?>> getAllowableParamAnnotations() {
ArrayList<Class<?>> retVal = new ArrayList<Class<?>>();
retVal.add(IdParam.class);
return retVal;
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
return isVread() ? RestfulOperationTypeEnum.VREAD : RestfulOperationTypeEnum.READ;
}
@Override
public ReturnTypeEnum getReturnType() {
return ReturnTypeEnum.RESOURCE;
}
@Override
public RestfulOperationSystemEnum getSystemOperationType() {
return null;
} }
@Override @Override
@ -107,7 +129,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
if (mySupportsVersion == false && myVersionIdIndex == null) { if (mySupportsVersion == false && myVersionIdIndex == null) {
return false; return false;
} }
if (theRequest.getId().hasVersionIdPart()==false) { if (theRequest.getId().hasVersionIdPart() == false) {
return false; return false;
} }
} else if (!StringUtils.isBlank(theRequest.getOperation())) { } else if (!StringUtils.isBlank(theRequest.getOperation())) {
@ -116,23 +138,6 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
return true; return true;
} }
@Override
public ReturnTypeEnum getReturnType() {
return ReturnTypeEnum.RESOURCE;
}
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
theMethodParams[myIdIndex] = theRequest.getId();
if (myVersionIdIndex != null) {
theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart());
}
Object response = invokeServerMethod(theMethodParams);
return toResourceList(response);
}
@Override @Override
public HttpGetClientInvocation invokeClient(Object[] theArgs) { public HttpGetClientInvocation invokeClient(Object[] theArgs) {
HttpGetClientInvocation retVal; HttpGetClientInvocation retVal;
@ -154,35 +159,9 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
return retVal; return retVal;
} }
public static HttpGetClientInvocation createVReadInvocation(IdDt theId, IdDt vid, String resourceName) {
return new HttpGetClientInvocation(resourceName, theId.getIdPart(), Constants.URL_TOKEN_HISTORY, vid.getIdPart());
}
public static HttpGetClientInvocation createReadInvocation(IdDt theId, String resourceName) {
if (theId.hasVersionIdPart()) {
return new HttpGetClientInvocation(resourceName, theId.getIdPart(), Constants.URL_TOKEN_HISTORY, theId.getVersionIdPart());
} else {
return new HttpGetClientInvocation(resourceName, theId.getIdPart());
}
}
@Override @Override
public RestfulOperationTypeEnum getResourceOperationType() { public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
return isVread() ? RestfulOperationTypeEnum.VREAD : RestfulOperationTypeEnum.READ; BaseServerResponseException {
}
@Override
public RestfulOperationSystemEnum getSystemOperationType() {
return null;
}
@Override
public boolean isBinary() {
return "Binary".equals(getResourceName());
}
@Override
public Object invokeClient(String theResponseMimeType, InputStream theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
byte[] contents = IOUtils.toByteArray(theResponseReader); byte[] contents = IOUtils.toByteArray(theResponseReader);
Binary resource = new Binary(theResponseMimeType, contents); Binary resource = new Binary(theResponseMimeType, contents);
@ -200,4 +179,37 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
throw new IllegalStateException("" + getMethodReturnType()); // should not happen throw new IllegalStateException("" + getMethodReturnType()); // should not happen
} }
@Override
public IBundleProvider invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
theMethodParams[myIdIndex] = theRequest.getId();
if (myVersionIdIndex != null) {
theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart());
}
Object response = invokeServerMethod(theMethodParams);
return toResourceList(response);
}
@Override
public boolean isBinary() {
return "Binary".equals(getResourceName());
}
public boolean isVread() {
return mySupportsVersion || myVersionIdIndex != null;
}
public static HttpGetClientInvocation createReadInvocation(IdDt theId, String resourceName) {
if (theId.hasVersionIdPart()) {
return new HttpGetClientInvocation(resourceName, theId.getIdPart(), Constants.URL_TOKEN_HISTORY, theId.getVersionIdPart());
} else {
return new HttpGetClientInvocation(resourceName, theId.getIdPart());
}
}
public static HttpGetClientInvocation createVReadInvocation(IdDt theId, IdDt vid, String resourceName) {
return new HttpGetClientInvocation(resourceName, theId.getIdPart(), Constants.URL_TOKEN_HISTORY, vid.getIdPart());
}
} }

View File

@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.io.Writer; import java.io.Writer;
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.net.URLEncoder; import java.net.URLEncoder;
@ -52,6 +53,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.eclipse.jetty.security.MappedLoginService.Anonymous;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -68,6 +70,7 @@ import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.method.Request;
@ -524,6 +527,21 @@ public class RestfulServer extends HttpServlet {
} }
} }
List<Class<?>> allowableParams = foundMethodBinding.getAllowableParamAnnotations();
if (allowableParams != null) {
for (Annotation[] nextParamAnnotations : m.getParameterAnnotations()) {
for (Annotation annotation : nextParamAnnotations) {
Package pack = annotation.annotationType().getPackage();
if (pack.equals(IdParam.class.getPackage())) {
if (!allowableParams.contains(annotation)) {
throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with "+ annotation);
}
}
}
}
}
resourceBinding.addMethod(foundMethodBinding); resourceBinding.addMethod(foundMethodBinding);
ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
} }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="614px" height="153px" version="1.1"><defs/><g transform="translate(0.5,0.5)"><path d="M 17 1 L 121 1 L 121 61 L 17 61 L 17 49 L 1 49 L 1 37 L 17 37 L 17 25 L 1 25 L 1 13 L 17 13 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 17 13 L 33 13 L 33 25 L 17 25 M 17 37 L 33 37 L 33 49 L 17 49" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-size="12px"><text x="39" y="35">hapi-fhir</text></g><rect x="321" y="1" width="110" height="60" rx="9" ry="9" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(323,24)"><switch><foreignObject pointer-events="all" width="106" height="15" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 106px; white-space: normal; text-align: center;">slf4j-api</div></foreignObject><text x="53" y="14" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><path d="M 121 31 L 315 31" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 320 31 L 313 35 L 315 31 L 313 28 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 67 91 L 171 91 L 171 151 L 67 151 L 67 139 L 51 139 L 51 127 L 67 127 L 67 115 L 51 115 L 51 103 L 67 103 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 67 103 L 83 103 L 83 115 L 67 115 M 67 127 L 83 127 L 83 139 L 67 139" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-size="12px"><text x="89" y="118">commons-</text><text x="89" y="132">httpclient</text></g><rect x="221" y="91" width="110" height="60" rx="9" ry="9" fill="#ffffff" stroke="#000000" pointer-events="none"/><g transform="translate(223,114)"><switch><foreignObject pointer-events="all" width="106" height="15" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 106px; white-space: normal; text-align: center;">jcl-over-slf4j</div></foreignObject><text x="53" y="14" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text></switch></g><path d="M 91 61 L 107 86" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 110 90 L 104 86 L 107 86 L 109 82 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 171 121 L 215 121" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 220 121 L 213 125 L 215 121 L 213 118 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 309 91 L 338 65" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 342 62 L 339 69 L 338 65 L 334 64 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 441 36 L 441 26 L 461 26 L 461 16 L 491 31 L 461 46 L 461 36 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><rect x="501" y="11" width="80" height="40" fill="none" stroke="none" pointer-events="none"/><g fill="#000000" font-family="Helvetica" font-size="12px"><text x="503" y="14">Underlying </text><text x="503" y="28">Logging</text><text x="503" y="42">Framework</text><text x="503" y="56">(logback, log4j, etc.)</text></g></g></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -23,7 +23,9 @@
Unfortunately HAPI is not immune to this issue. Unfortunately HAPI is not immune to this issue.
</p> </p>
<subsection name="SLF4j"> <subsection name="Configuring HAPI's Logging - SLF4j">
<img src="svg/hapi-fhir-logging.svg" width="723" height="273" alt="Logging arch diagram" align="right"/>
<p> <p>
HAPI uses HAPI uses
@ -67,6 +69,8 @@
<subsection name="Commons-Logging"> <subsection name="Commons-Logging">
<img src="svg/hapi-fhir-logging-complete.svg" width="614" height="153" alt="Logging arch diagram" align="right"/>
<p> <p>
Note that HAPI's client uses Apache HttpComponents Client internally, and that Note that HAPI's client uses Apache HttpComponents Client internally, and that
library uses Apache Commons Logging as a logging facade. The recommended approach to library uses Apache Commons Logging as a logging facade. The recommended approach to
@ -76,6 +80,21 @@
configured to. configured to.
</p> </p>
<p>
The diagram at the right shows the chain of command for logging under this scheme.
</p>
<p>
Note that some popular libraries (e.g. Spring Framework) also use commons-logging
for logging. As such they may include a commons-logging JAR automatically as
a transitive dependency in Maven. If you are using jcl-over-slf4j and it isn't
working correctly, it is often worth checking the list of JARs included in your
application to see whether commons-logging has also been added. It can then be specifically
excluded in Maven.
</p>
<br clear="all"/>
</subsection> </subsection>
</section> </section>

View File

@ -20,6 +20,7 @@ import ca.uhn.fhir.util.ElementUtil;
@ResourceDef(name="Patient") @ResourceDef(name="Patient")
public class DummyPatientWithExtensions extends Patient { public class DummyPatientWithExtensions extends Patient {
/** /**
* Each extension is defined in a field. Any valid HAPI Data Type * Each extension is defined in a field. Any valid HAPI Data Type
* can be used for the field type. Note that the [name=""] attribute * can be used for the field type. Note that the [name=""] attribute

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*; import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -13,6 +14,8 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.param.StringParam;
public class ServerInvalidDefinitionTest { public class ServerInvalidDefinitionTest {
@ -43,6 +46,19 @@ public class ServerInvalidDefinitionTest {
} }
} }
@Test
public void testReadMethodWithSearchParameters() {
RestfulServer srv = new RestfulServer();
srv.setResourceProviders(new ReadMethodWithSearchParamProvider());
try {
srv.init();
fail();
} catch (ServletException e) {
assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException"));
}
}
/** /**
* Normal, should initialize properly * Normal, should initialize properly
*/ */
@ -69,6 +85,22 @@ public class ServerInvalidDefinitionTest {
} }
public static class ReadMethodWithSearchParamProvider implements IResourceProvider
{
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@SuppressWarnings("unused")
@Read
public Patient read(@IdParam IdDt theId, @RequiredParam(name="aaa") StringParam theParam) {
return null;
}
}
public static class NonInstantiableTypeForResourceProvider implements IResourceProvider public static class NonInstantiableTypeForResourceProvider implements IResourceProvider
{ {

View File

@ -12,7 +12,7 @@
<dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base"> <dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.6-SNAPSHOT/hapi-fhir-testpage-overlay-0.6-SNAPSHOT.war?unpackFolder=target/m2e-wtp/overlays&amp;includes=**/**&amp;excludes=META-INF/MANIFEST.MF"> <dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependency-type>consumes</dependency-type> <dependency-type>consumes</dependency-type>
</dependent-module> </dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;excludes=META-INF/MANIFEST.MF"> <dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">

View File

@ -6,7 +6,7 @@
<dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base"> <dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
<dependency-type>uses</dependency-type> <dependency-type>uses</dependency-type>
</dependent-module> </dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.6-SNAPSHOT/hapi-fhir-testpage-overlay-0.6-SNAPSHOT.war?unpackFolder=target/m2e-wtp/overlays&amp;includes=**/**&amp;excludes=META-INF/MANIFEST.MF"> <dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependency-type>consumes</dependency-type> <dependency-type>consumes</dependency-type>
</dependent-module> </dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;excludes=META-INF/MANIFEST.MF"> <dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">