Unescape Read ID and Version IDs
This commit is contained in:
parent
9828d27eec
commit
46b6979125
|
@ -30,6 +30,7 @@ import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -51,6 +52,7 @@ import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringEscapeUtils;
|
||||||
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;
|
||||||
|
@ -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.exceptions.NotModifiedException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.util.ReflectionUtil;
|
import ca.uhn.fhir.util.ReflectionUtil;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import ca.uhn.fhir.util.VersionUtil;
|
import ca.uhn.fhir.util.VersionUtil;
|
||||||
|
|
||||||
public class RestfulServer extends HttpServlet {
|
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
|
* 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.
|
* (metadata) statement if one has been explicitly defined.
|
||||||
* <p>
|
* <p>
|
||||||
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code>
|
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
|
||||||
* to use the appropriate one for the given FHIR version.
|
* set to <code>null</code> to use the appropriate one for the given FHIR version.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public Object getServerConformanceProvider() {
|
public Object getServerConformanceProvider() {
|
||||||
|
@ -539,7 +542,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
if (nextString.startsWith("_")) {
|
if (nextString.startsWith("_")) {
|
||||||
operation = nextString;
|
operation = nextString;
|
||||||
} else {
|
} 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) {
|
if (id == null) {
|
||||||
throw new InvalidRequestException("Don't know how to handle request path: " + requestPath);
|
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 {
|
} else {
|
||||||
operation = Constants.PARAM_HISTORY;
|
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
|
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
||||||
* (metadata) statement.
|
* (metadata) statement.
|
||||||
* <p>
|
* <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>
|
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can
|
||||||
* if you do not wish to export a conformance statement.
|
* be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
|
||||||
* </p>
|
* </p>
|
||||||
* Note that this method can only be called before the server is initialized.
|
* 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
|
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
|
||||||
* implementation
|
* implementation
|
||||||
*
|
*
|
||||||
* @param requestFullPath the full request path
|
* @param requestFullPath
|
||||||
* @param servletContextPath the servelet context path
|
* the full request path
|
||||||
* @param servletPath the servelet path
|
* @param servletContextPath
|
||||||
|
* the servelet context path
|
||||||
|
* @param servletPath
|
||||||
|
* the servelet path
|
||||||
* @return created resource path
|
* @return created resource path
|
||||||
*/
|
*/
|
||||||
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
|
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package ca.uhn.fhir.util;
|
package ca.uhn.fhir.util;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -27,8 +29,8 @@ public class UrlUtil {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UrlUtil.class);
|
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
|
* Resolve a relative URL - THIS METHOD WILL NOT FAIL but will log a warning and return theEndpoint if the input is
|
||||||
* and return theEndpoint if the input is invalid.
|
* invalid.
|
||||||
*/
|
*/
|
||||||
public static String constructAbsoluteUrl(String theBase, String theEndpoint) {
|
public static String constructAbsoluteUrl(String theBase, String theEndpoint) {
|
||||||
if (theEndpoint == null) {
|
if (theEndpoint == null) {
|
||||||
|
@ -78,7 +80,7 @@ public class UrlUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theExtensionUrl.length() > parentLastSlashIdx) {
|
if (theExtensionUrl.length() > parentLastSlashIdx) {
|
||||||
return theExtensionUrl.substring(parentLastSlashIdx+1);
|
return theExtensionUrl.substring(parentLastSlashIdx + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return theExtensionUrl;
|
return theExtensionUrl;
|
||||||
|
@ -124,4 +126,18 @@ public class UrlUtil {
|
||||||
return true;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.stringContainsInOrder;
|
import static org.hamcrest.Matchers.stringContainsInOrder;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
@ -20,6 +21,8 @@ import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.google.common.net.UrlEscapers;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.BaseResource;
|
import ca.uhn.fhir.model.api.BaseResource;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
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
|
@AfterClass
|
||||||
public static void afterClass() throws Exception {
|
public static void afterClass() throws Exception {
|
||||||
ourServer.stop();
|
ourServer.stop();
|
||||||
|
@ -166,12 +188,12 @@ public class ReadTest {
|
||||||
ourPort = PortUtil.findFreePort();
|
ourPort = PortUtil.findFreePort();
|
||||||
ourServer = new Server(ourPort);
|
ourServer = new Server(ourPort);
|
||||||
|
|
||||||
DummyProvider patientProvider = new DummyProvider();
|
PatientProvider patientProvider = new PatientProvider();
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
RestfulServer servlet = new RestfulServer();
|
RestfulServer servlet = new RestfulServer();
|
||||||
ourCtx = servlet.getFhirContext();
|
ourCtx = servlet.getFhirContext();
|
||||||
servlet.setResourceProviders(patientProvider, new DummyBinaryProvider(), new OrganizationProviderWithAbstractReturnType());
|
servlet.setResourceProviders(patientProvider, new BinaryProvider(), new OrganizationProviderWithAbstractReturnType());
|
||||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
ourServer.setHandler(proxyHandler);
|
ourServer.setHandler(proxyHandler);
|
||||||
|
@ -187,7 +209,7 @@ public class ReadTest {
|
||||||
/**
|
/**
|
||||||
* Created by dsotnikov on 2/25/2014.
|
* Created by dsotnikov on 2/25/2014.
|
||||||
*/
|
*/
|
||||||
public static class DummyProvider implements IResourceProvider {
|
public static class PatientProvider implements IResourceProvider {
|
||||||
|
|
||||||
@Read(version = true)
|
@Read(version = true)
|
||||||
public Patient read(@IdParam IdDt theId) {
|
public Patient read(@IdParam IdDt theId) {
|
||||||
|
@ -210,7 +232,7 @@ public class ReadTest {
|
||||||
public static class OrganizationProviderWithAbstractReturnType implements IResourceProvider {
|
public static class OrganizationProviderWithAbstractReturnType implements IResourceProvider {
|
||||||
|
|
||||||
@Read(version = true)
|
@Read(version = true)
|
||||||
public BaseResource findPatient(@IdParam IdDt theId) {
|
public BaseResource findOrganization(@IdParam IdDt theId) {
|
||||||
Organization org = new Organization();
|
Organization org = new Organization();
|
||||||
org.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
|
org.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
|
||||||
org.setId("Organization/1/_history/1");
|
org.setId("Organization/1/_history/1");
|
||||||
|
@ -227,7 +249,7 @@ public class ReadTest {
|
||||||
/**
|
/**
|
||||||
* Created by dsotnikov on 2/25/2014.
|
* Created by dsotnikov on 2/25/2014.
|
||||||
*/
|
*/
|
||||||
public static class DummyBinaryProvider implements IResourceProvider {
|
public static class BinaryProvider implements IResourceProvider {
|
||||||
|
|
||||||
@Read(version = true)
|
@Read(version = true)
|
||||||
public Binary findPatient(@IdParam IdDt theId) {
|
public Binary findPatient(@IdParam IdDt theId) {
|
||||||
|
|
|
@ -27,12 +27,21 @@
|
||||||
<b>Important Note: </b>
|
<b>Important Note: </b>
|
||||||
This implementation uses a fairly simple table design, with a
|
This implementation uses a fairly simple table design, with a
|
||||||
single table being used to hold resource bodies (which are stored as
|
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
|
history details, etc. This design is only one of many possible ways
|
||||||
of designing a FHIR server so it is worth considering whether it
|
of designing a FHIR server so it is worth considering whether it
|
||||||
is appropriate for the problem you are trying to solve.
|
is appropriate for the problem you are trying to solve.
|
||||||
</p>
|
</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>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue