Unescape Read ID and Version IDs

This commit is contained in:
jamesagnew 2015-02-10 14:57:16 -05:00
parent 9828d27eec
commit 46b6979125
4 changed files with 91 additions and 38 deletions

View File

@ -30,6 +30,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
@ -51,6 +52,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
@ -91,6 +93,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.VersionUtil;
public class RestfulServer extends HttpServlet {
@ -385,8 +388,8 @@ public class RestfulServer extends HttpServlet {
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement if one has been explicitly defined.
* <p>
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code>
* to use the appropriate one for the given FHIR version.
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
* set to <code>null</code> to use the appropriate one for the given FHIR version.
* </p>
*/
public Object getServerConformanceProvider() {
@ -539,7 +542,7 @@ public class RestfulServer extends HttpServlet {
if (nextString.startsWith("_")) {
operation = nextString;
} else {
id = new IdDt(resourceName, nextString);
id = new IdDt(resourceName, UrlUtil.unescape(nextString));
}
}
@ -551,7 +554,7 @@ public class RestfulServer extends HttpServlet {
if (id == null) {
throw new InvalidRequestException("Don't know how to handle request path: " + requestPath);
}
id = new IdDt(resourceName + "/" + id.getIdPart() + "/_history/" + versionString);
id = new IdDt(resourceName, id.getIdPart(), UrlUtil.unescape(versionString));
} else {
operation = Constants.PARAM_HISTORY;
}
@ -984,8 +987,8 @@ public class RestfulServer extends HttpServlet {
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* <p>
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can
* be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
@ -1534,9 +1537,12 @@ public class RestfulServer extends HttpServlet {
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
* implementation
*
* @param requestFullPath the full request path
* @param servletContextPath the servelet context path
* @param servletPath the servelet path
* @param requestFullPath
* the full request path
* @param servletContextPath
* the servelet context path
* @param servletPath
* the servelet path
* @return created resource path
*/
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {

View File

@ -1,7 +1,9 @@
package ca.uhn.fhir.util;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
/*
* #%L
@ -27,8 +29,8 @@ public class UrlUtil {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UrlUtil.class);
/**
* Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning
* and return theEndpoint if the input is invalid.
* Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is
* invalid.
*/
public static String constructAbsoluteUrl(String theBase, String theEndpoint) {
if (theEndpoint == null) {
@ -124,4 +126,18 @@ public class UrlUtil {
return true;
}
public static String unescape(String theString) {
if (theString == null) {
return null;
}
if (theString.indexOf('%') == -1) {
return theString;
}
try {
return URLDecoder.decode(theString, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("UTF-8 not supported, this shouldn't happen", e);
}
}
}

View File

@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.*;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@ -20,6 +21,8 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.net.UrlEscapers;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.BaseResource;
import ca.uhn.fhir.model.api.IResource;
@ -156,6 +159,25 @@ public class ReadTest {
}
}
@Test
public void testReadWithEscapedCharsInId() throws Exception {
String id = "ABC!@#$%DEF";
String idEscaped = URLEncoder.encode(id, "UTF-8");
String vid = "GHI:/:/JKL";
String vidEscaped = URLEncoder.encode(vid, "UTF-8");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/" + idEscaped + "/_history/" + vidEscaped);
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class, responseContent).getIdentifierFirstRep();
assertEquals(id, dt.getSystem().getValueAsString());
assertEquals(vid, dt.getValue().getValueAsString());
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
@ -166,12 +188,12 @@ public class ReadTest {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyProvider patientProvider = new DummyProvider();
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
ourCtx = servlet.getFhirContext();
servlet.setResourceProviders(patientProvider, new DummyBinaryProvider(), new OrganizationProviderWithAbstractReturnType());
servlet.setResourceProviders(patientProvider, new BinaryProvider(), new OrganizationProviderWithAbstractReturnType());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
@ -187,7 +209,7 @@ public class ReadTest {
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyProvider implements IResourceProvider {
public static class PatientProvider implements IResourceProvider {
@Read(version = true)
public Patient read(@IdParam IdDt theId) {
@ -210,7 +232,7 @@ public class ReadTest {
public static class OrganizationProviderWithAbstractReturnType implements IResourceProvider {
@Read(version = true)
public BaseResource findPatient(@IdParam IdDt theId) {
public BaseResource findOrganization(@IdParam IdDt theId) {
Organization org = new Organization();
org.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
org.setId("Organization/1/_history/1");
@ -227,7 +249,7 @@ public class ReadTest {
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyBinaryProvider implements IResourceProvider {
public static class BinaryProvider implements IResourceProvider {
@Read(version = true)
public Binary findPatient(@IdParam IdDt theId) {

View File

@ -27,12 +27,21 @@
<b>Important Note: </b>
This implementation uses a fairly simple table design, with a
single table being used to hold resource bodies (which are stored as
GZipped CLOBs) and a set of tables to hold search indexes, tags,
CLOBs, optionally GZipped to save space) and a set of tables to hold search indexes, tags,
history details, etc. This design is only one of many possible ways
of designing a FHIR server so it is worth considering whether it
is appropriate for the problem you are trying to solve.
</p>
<subsection name="Getting Started">
<p>
The easiest way to get started with HAPI's JPA server module is
to begin with the example project.
</p>
</subsection>
</section>
</body>