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.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;
|
||||
}
|
||||
|
@ -797,7 +800,7 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
findResourceMethods(getServerProfilesProvider());
|
||||
|
||||
|
||||
Object confProvider = getServerConformanceProvider();
|
||||
if (confProvider == null) {
|
||||
confProvider = myFhirContext.getVersion().createServerConformanceProvider(this);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -1148,13 +1151,13 @@ public class RestfulServer extends HttpServlet {
|
|||
|
||||
List<IResource> includedResources = new ArrayList<IResource>();
|
||||
Set<IdDt> addedResourceIds = new HashSet<IdDt>();
|
||||
|
||||
|
||||
for (IResource next : theResult) {
|
||||
if (next.getId().isEmpty() == false) {
|
||||
addedResourceIds.add(next.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (IResource next : theResult) {
|
||||
|
||||
Set<String> containedIds = new HashSet<String>();
|
||||
|
@ -1214,7 +1217,7 @@ public class RestfulServer extends HttpServlet {
|
|||
} while (references.isEmpty() == false);
|
||||
|
||||
bundle.addResource(next, theContext, theServerBase);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
@ -25,10 +27,10 @@ import java.net.URL;
|
|||
|
||||
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) {
|
||||
|
@ -40,7 +42,7 @@ public class UrlUtil {
|
|||
if (theBase == null) {
|
||||
return theEndpoint;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return new URL(new URL(theBase), theEndpoint).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
|
@ -48,7 +50,7 @@ public class UrlUtil {
|
|||
return theEndpoint;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isAbsolute(String theValue) {
|
||||
String value = theValue.toLowerCase();
|
||||
return value.startsWith("http://") || value.startsWith("https://");
|
||||
|
@ -61,26 +63,26 @@ public class UrlUtil {
|
|||
if (theExtensionUrl == null) {
|
||||
return theExtensionUrl;
|
||||
}
|
||||
|
||||
|
||||
int parentLastSlashIdx = theParentExtensionUrl.lastIndexOf('/');
|
||||
int childLastSlashIdx = theExtensionUrl.lastIndexOf('/');
|
||||
|
||||
|
||||
if (parentLastSlashIdx == -1 || childLastSlashIdx == -1) {
|
||||
return theExtensionUrl;
|
||||
}
|
||||
|
||||
|
||||
if (parentLastSlashIdx != childLastSlashIdx) {
|
||||
return theExtensionUrl;
|
||||
}
|
||||
|
||||
|
||||
if (!theParentExtensionUrl.substring(0, parentLastSlashIdx).equals(theExtensionUrl.substring(0, parentLastSlashIdx))) {
|
||||
return theExtensionUrl;
|
||||
}
|
||||
|
||||
|
||||
if (theExtensionUrl.length() > parentLastSlashIdx) {
|
||||
return theExtensionUrl.substring(parentLastSlashIdx+1);
|
||||
return theExtensionUrl.substring(parentLastSlashIdx + 1);
|
||||
}
|
||||
|
||||
|
||||
return theExtensionUrl;
|
||||
}
|
||||
|
||||
|
@ -88,7 +90,7 @@ public class UrlUtil {
|
|||
if (theUrl == null || theUrl.length() < 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
String url = theUrl.toLowerCase();
|
||||
if (url.charAt(0) != 'h') {
|
||||
return false;
|
||||
|
@ -113,7 +115,7 @@ public class UrlUtil {
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (url.charAt(slashOffset) != '/') {
|
||||
return false;
|
||||
}
|
||||
|
@ -123,5 +125,19 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -50,7 +53,7 @@ public class ReadTest {
|
|||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class, responseContent).getIdentifierFirstRep();
|
||||
|
||||
|
@ -60,7 +63,7 @@ public class ReadTest {
|
|||
Header cl = status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION_LC);
|
||||
assertNotNull(cl);
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/1/_history/1", cl.getValue());
|
||||
|
||||
|
||||
assertThat(responseContent, stringContainsInOrder("1", "\""));
|
||||
assertThat(responseContent, not(stringContainsInOrder("1", "\"", "1")));
|
||||
}
|
||||
|
@ -72,7 +75,7 @@ public class ReadTest {
|
|||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
IdentifierDt dt = ourCtx.newJsonParser().parseResource(Patient.class, responseContent).getIdentifierFirstRep();
|
||||
|
||||
|
@ -82,7 +85,7 @@ public class ReadTest {
|
|||
Header cl = status.getFirstHeader(Constants.HEADER_CONTENT_LOCATION_LC);
|
||||
assertNotNull(cl);
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/1/_history/1", cl.getValue());
|
||||
|
||||
|
||||
assertThat(responseContent, stringContainsInOrder("1", "\""));
|
||||
assertThat(responseContent, not(stringContainsInOrder("1", "\"", "1")));
|
||||
}
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue