Improve nexted search/read handing in transactions in JPA server

This commit is contained in:
James Agnew 2015-10-20 10:35:18 -04:00
parent 9b5598e2ab
commit ae416dcd62
17 changed files with 604 additions and 589 deletions

View File

@ -15,6 +15,11 @@
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec> </buildSpec>
<natures> <natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature> <nature>org.eclipse.jem.workbench.JavaEMFNature</nature>

View File

@ -67,7 +67,7 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> { public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> {
protected static final Set<String> ALLOWED_PARAMS; protected static final Set<String> ALLOWED_PARAMS;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReturningMethodBinding.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReturningMethodBinding.class);
@ -102,8 +102,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod); Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
if (collectionType != null) { if (collectionType != null) {
if (!Object.class.equals(collectionType) && !IBaseResource.class.isAssignableFrom(collectionType)) { if (!Object.class.equals(collectionType) && !IBaseResource.class.isAssignableFrom(collectionType)) {
throw new ConfigurationException("Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: " throw new ConfigurationException(
+ collectionType); "Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: " + collectionType);
} }
} }
myResourceListCollectionType = collectionType; myResourceListCollectionType = collectionType;
@ -121,8 +121,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) { } else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME; myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME;
} else { } else {
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " throw new ConfigurationException(
+ theMethod.getDeclaringClass().getCanonicalName()); "Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
} }
if (theReturnResourceType != null) { if (theReturnResourceType != null) {
@ -233,29 +233,52 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
throw new IllegalStateException("Should not get here!"); throw new IllegalStateException("Should not get here!");
} }
public abstract Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
@Override @Override
public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
// Narrative mode
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
// Determine response encoding
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), theServer.getDefaultResponseEncoding());
// Is this request coming from a browser
String uaHeader = theRequest.getServletRequest().getHeader("user-agent");
boolean requestIsBrowser = false;
if (uaHeader != null && uaHeader.contains("Mozilla")) {
requestIsBrowser = true;
}
byte[] requestContents = loadRequestContents(theRequest); byte[] requestContents = loadRequestContents(theRequest);
final ResourceOrDstu1Bundle responseObject = invokeServer(theServer, theRequest, requestContents);
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
if (responseObject.getResource() != null) {
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, responseObject.getResource(), theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
return;
}
}
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
HttpServletResponse response = theRequest.getServletResponse();
RestfulServerUtils.streamResponseAsResource(theServer, response, responseObject.getResource(), prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(),
isAddContentLocationHeader(), theRequest);
} else {
// Is this request coming from a browser
String uaHeader = theRequest.getServletRequest().getHeader("user-agent");
boolean requestIsBrowser = false;
if (uaHeader != null && uaHeader.contains("Mozilla")) {
requestIsBrowser = true;
}
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, responseObject.getDstu1Bundle(), theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
HttpServletResponse response = theRequest.getServletResponse();
RestfulServerUtils.streamResponseAsBundle(theServer, response, responseObject.getDstu1Bundle(), theRequest.getFhirServerBase(), summaryMode, theRequest.isRespondGzip(), requestIsBrowser,
theRequest);
}
}
public ResourceOrDstu1Bundle invokeServer(RestfulServer theServer, RequestDetails theRequest, byte[] requestContents) {
// Method params // Method params
Object[] params = new Object[getParameters().size()]; Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) { for (int i = 0; i < getParameters().size(); i++) {
@ -267,9 +290,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
Object resultObj = invokeServer(theServer, theRequest, params); Object resultObj = invokeServer(theServer, theRequest, params);
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest()); Integer count = RestfulServerUtils.extractCountParameter(theRequest);
boolean respondGzip = theRequest.isRespondGzip();
HttpServletResponse response = theRequest.getServletResponse(); final ResourceOrDstu1Bundle responseObject;
switch (getReturnType()) { switch (getReturnType()) {
case BUNDLE: { case BUNDLE: {
@ -323,17 +347,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
bundleFactory.initializeWithBundleResource(resource); bundleFactory.initializeWithBundleResource(resource);
bundleFactory.addRootPropertiesToBundle(null, theRequest.getFhirServerBase(), linkSelf, count, getResponseBundleType(), lastUpdated); bundleFactory.addRootPropertiesToBundle(null, theRequest.getFhirServerBase(), linkSelf, count, getResponseBundleType(), lastUpdated);
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { responseObject = new ResourceOrDstu1Bundle(resource);
IServerInterceptor next = theServer.getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip,
isAddContentLocationHeader(), theRequest);
break; break;
} else { } else {
Set<Include> includes = getRequestIncludesFromParams(params); Set<Include> includes = getRequestIncludesFromParams(params);
@ -344,32 +358,18 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} }
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory(); IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), theServer.getDefaultResponseEncoding());
EncodingEnum linkEncoding = theRequest.getParameters().containsKey(Constants.PARAM_FORMAT) ? responseEncoding : null; EncodingEnum linkEncoding = theRequest.getParameters().containsKey(Constants.PARAM_FORMAT) ? responseEncoding : null;
bundleFactory.initializeBundleFromBundleProvider(theServer, result, linkEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, 0, count, null, getResponseBundleType(),
includes); boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
bundleFactory.initializeBundleFromBundleProvider(theServer, result, linkEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, 0, count, null, getResponseBundleType(), includes);
Bundle bundle = bundleFactory.getDstu1Bundle(); Bundle bundle = bundleFactory.getDstu1Bundle();
if (bundle != null) { if (bundle != null) {
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { responseObject = new ResourceOrDstu1Bundle(bundle);
IServerInterceptor next = theServer.getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
RestfulServerUtils.streamResponseAsBundle(theServer, response, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest);
} else { } else {
IBaseResource resBundle = bundleFactory.getResourceBundle(); IBaseResource resBundle = bundleFactory.getResourceBundle();
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { responseObject = new ResourceOrDstu1Bundle(resBundle);
IServerInterceptor next = theServer.getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, resBundle, theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
RestfulServerUtils.streamResponseAsResource(theServer, response, resBundle, prettyPrint, summaryMode,
Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), isAddContentLocationHeader(), theRequest);
} }
break; break;
@ -384,22 +384,17 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} }
IBaseResource resource = result.getResources(0, 1).get(0); IBaseResource resource = result.getResources(0, 1).get(0);
responseObject = new ResourceOrDstu1Bundle(resource);
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
IServerInterceptor next = theServer.getInterceptors().get(i);
boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse());
if (!continueProcessing) {
return;
}
}
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip,
isAddContentLocationHeader(), theRequest);
break; break;
} }
default:
throw new IllegalStateException(); // should not happen
} }
return responseObject;
} }
public abstract Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
/** /**
* Should the response include a Content-Location header. Search method bunding (and any others?) may override this to disable the content-location, since it doesn't make sense * Should the response include a Content-Location header. Search method bunding (and any others?) may override this to disable the content-location, since it doesn't make sense
*/ */
@ -412,7 +407,32 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} }
public enum MethodReturnTypeEnum { public enum MethodReturnTypeEnum {
BUNDLE, BUNDLE_PROVIDER, BUNDLE_RESOURCE, LIST_OF_RESOURCES, RESOURCE, METHOD_OUTCOME BUNDLE, BUNDLE_PROVIDER, BUNDLE_RESOURCE, LIST_OF_RESOURCES, METHOD_OUTCOME, RESOURCE
}
public static class ResourceOrDstu1Bundle {
private final Bundle myDstu1Bundle;
private final IBaseResource myResource;
public ResourceOrDstu1Bundle(Bundle theBundle) {
myDstu1Bundle = theBundle;
myResource = null;
}
public ResourceOrDstu1Bundle(IBaseResource theResource) {
myResource = theResource;
myDstu1Bundle = null;
}
public Bundle getDstu1Bundle() {
return myDstu1Bundle;
}
public IBaseResource getResource() {
return myResource;
}
} }
public enum ReturnTypeEnum { public enum ReturnTypeEnum {

View File

@ -214,7 +214,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
IBundleProvider retVal = toResourceList(response); IBundleProvider retVal = toResourceList(response);
if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) { if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) {
String ifNoneMatch = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_MATCH_LC); String ifNoneMatch = theRequest.getFirstHeader(Constants.HEADER_IF_NONE_MATCH_LC);
if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) { if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) {
List<IBaseResource> responseResources = retVal.getResources(0, 1); List<IBaseResource> responseResources = retVal.getResources(0, 1);
IBaseResource responseResource = responseResources.get(0); IBaseResource responseResource = responseResources.get(0);

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.rest.server.RestfulServer;
public class RequestDetails { public class RequestDetails {
private Map<String, ArrayList<String>> myHeaders = new HashMap<String, ArrayList<String>>();
private String myCompartmentName; private String myCompartmentName;
private String myCompleteUrl; private String myCompleteUrl;
private String myFhirServerBase; private String myFhirServerBase;
@ -230,4 +231,22 @@ public class RequestDetails {
return retVal; return retVal;
} }
public void addHeader(String theName, String theValue) {
String lowerCase = theName.toLowerCase();
ArrayList<String> list = myHeaders.get(lowerCase);
if (list == null) {
list = new ArrayList<String>();
myHeaders.put(lowerCase, list);
}
list.add(theValue);
}
public String getFirstHeader(String theName) {
ArrayList<String> list = myHeaders.get(theName.toLowerCase());
if (list == null || list.isEmpty()) {
return null;
}
return list.get(0);
}
} }

View File

@ -52,9 +52,6 @@ import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class SearchMethodBinding extends BaseResourceReturningMethodBinding { public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);

View File

@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -477,14 +478,14 @@ public class RestfulServer extends HttpServlet {
return; return;
} }
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest()); Integer count = RestfulServerUtils.extractCountParameter(theRequest);
if (count == null) { if (count == null) {
count = getPagingProvider().getDefaultPageSize(); count = getPagingProvider().getDefaultPageSize();
} else if (count > getPagingProvider().getMaximumPageSize()) { } else if (count > getPagingProvider().getMaximumPageSize()) {
count = getPagingProvider().getMaximumPageSize(); count = getPagingProvider().getMaximumPageSize();
} }
Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest.getServletRequest(), Constants.PARAM_PAGINGOFFSET); Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_PAGINGOFFSET);
if (offsetI == null || offsetI < 0) { if (offsetI == null || offsetI < 0) {
offsetI = 0; offsetI = 0;
} }
@ -545,6 +546,12 @@ public class RestfulServer extends HttpServlet {
requestDetails.setRequestType(theRequestType); requestDetails.setRequestType(theRequestType);
requestDetails.setServletRequest(theRequest); requestDetails.setServletRequest(theRequest);
requestDetails.setServletResponse(theResponse); requestDetails.setServletResponse(theResponse);
for (Enumeration<String> iter = theRequest.getHeaderNames(); iter.hasMoreElements(); ) {
String nextName = iter.nextElement();
for (Enumeration<String> valueIter = theRequest.getHeaders(nextName); valueIter.hasMoreElements();) {
requestDetails.addHeader(nextName, valueIter.nextElement());
}
}
try { try {
@ -556,7 +563,6 @@ public class RestfulServer extends HttpServlet {
} }
} }
String resourceName = null;
String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI()); String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
String servletPath = StringUtils.defaultString(theRequest.getServletPath()); String servletPath = StringUtils.defaultString(theRequest.getServletPath());
StringBuffer requestUrl = theRequest.getRequestURL(); StringBuffer requestUrl = theRequest.getRequestURL();
@ -572,11 +578,9 @@ public class RestfulServer extends HttpServlet {
ourLog.trace("Context Path: {}", servletContextPath); ourLog.trace("Context Path: {}", servletContextPath);
} }
IdDt id = null;
String operation = null;
String compartment = null;
String requestPath = getRequestPath(requestFullPath, servletContextPath, servletPath); String requestPath = getRequestPath(requestFullPath, servletContextPath, servletPath);
if (requestPath.length() > 0 && requestPath.charAt(0) == '/') { if (requestPath.length() > 0 && requestPath.charAt(0) == '/') {
requestPath = requestPath.substring(1); requestPath = requestPath.substring(1);
} }
@ -588,84 +592,16 @@ public class RestfulServer extends HttpServlet {
Map<String, String[]> params = new HashMap<String, String[]>(theRequest.getParameterMap()); Map<String, String[]> params = new HashMap<String, String[]>(theRequest.getParameterMap());
requestDetails.setParameters(params); requestDetails.setParameters(params);
StringTokenizer tok = new StringTokenizer(requestPath, "/"); IdDt id;
if (tok.hasMoreTokens()) { populateRequestDetailsFromRequestPath(requestDetails, requestPath);
resourceName = tok.nextToken();
if (partIsOperation(resourceName)) {
operation = resourceName;
resourceName = null;
}
}
requestDetails.setResourceName(resourceName);
ResourceBinding resourceBinding = null;
BaseMethodBinding<?> resourceMethod = null;
if (Constants.URL_TOKEN_METADATA.equals(resourceName) || theRequestType == RequestTypeEnum.OPTIONS) {
resourceMethod = myServerConformanceMethod;
} else if (resourceName == null) {
resourceBinding = myServerBinding;
} else {
resourceBinding = myResourceNameToBinding.get(resourceName);
if (resourceBinding == null) {
throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToBinding.keySet());
}
}
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (partIsOperation(nextString)) {
operation = nextString;
} else {
id = new IdDt(resourceName, UrlUtil.unescape(nextString));
}
}
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (nextString.equals(Constants.PARAM_HISTORY)) {
if (tok.hasMoreTokens()) {
String versionString = tok.nextToken();
if (id == null) {
throw new InvalidRequestException("Don't know how to handle request path: " + requestPath);
}
id = new IdDt(resourceName, id.getIdPart(), UrlUtil.unescape(versionString));
} else {
operation = Constants.PARAM_HISTORY;
}
} else if (partIsOperation(nextString)) {
if (operation != null) {
throw new InvalidRequestException("URL Path contains two operations: " + requestPath);
}
operation = nextString;
} else {
compartment = nextString;
}
}
// Secondary is for things like ..../_tags/_delete
String secondaryOperation = null;
while (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (operation == null) {
operation = nextString;
} else if (secondaryOperation == null) {
secondaryOperation = nextString;
} else {
throw new InvalidRequestException("URL path has unexpected token '" + nextString + "' at the end: " + requestPath);
}
}
if (theRequestType == RequestTypeEnum.PUT) { if (theRequestType == RequestTypeEnum.PUT) {
String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION);
if (contentLocation != null) { if (contentLocation != null) {
id = new IdDt(contentLocation); id = new IdDt(contentLocation);
requestDetails.setId(id);
} }
} }
requestDetails.setId(id);
requestDetails.setOperation(operation);
requestDetails.setSecondaryOperation(secondaryOperation);
requestDetails.setCompartmentName(compartment);
String acceptEncoding = theRequest.getHeader(Constants.HEADER_ACCEPT_ENCODING); String acceptEncoding = theRequest.getHeader(Constants.HEADER_ACCEPT_ENCODING);
boolean respondGzip = false; boolean respondGzip = false;
@ -696,18 +632,7 @@ public class RestfulServer extends HttpServlet {
return; return;
} }
if (resourceMethod == null) { BaseMethodBinding<?> resourceMethod = determineResourceMethod(requestDetails, requestPath);
if (resourceBinding != null) {
resourceMethod = resourceBinding.getMethod(requestDetails);
}
}
if (resourceMethod == null) {
if (isBlank(requestPath)) {
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "rootRequest"));
} else {
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", theRequestType.name(), requestPath, params.keySet()));
}
}
requestDetails.setRestOperationType(resourceMethod.getRestOperationType()); requestDetails.setRestOperationType(resourceMethod.getRestOperationType());
@ -806,6 +731,105 @@ public class RestfulServer extends HttpServlet {
} }
} }
public BaseMethodBinding<?> determineResourceMethod(RequestDetails requestDetails, String requestPath) {
RequestTypeEnum requestType = requestDetails.getRequestType();
ResourceBinding resourceBinding = null;
BaseMethodBinding<?> resourceMethod = null;
String resourceName = requestDetails.getResourceName();
if (Constants.URL_TOKEN_METADATA.equals(resourceName) || requestType == RequestTypeEnum.OPTIONS) {
resourceMethod = myServerConformanceMethod;
} else if (resourceName == null) {
resourceBinding = myServerBinding;
} else {
resourceBinding = myResourceNameToBinding.get(resourceName);
if (resourceBinding == null) {
throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToBinding.keySet());
}
}
if (resourceMethod == null) {
if (resourceBinding != null) {
resourceMethod = resourceBinding.getMethod(requestDetails);
}
}
if (resourceMethod == null) {
if (isBlank(requestPath)) {
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "rootRequest"));
} else {
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", requestType.name(), requestPath, requestDetails.getParameters().keySet()));
}
}
return resourceMethod;
}
public void populateRequestDetailsFromRequestPath(RequestDetails theRequestDetails, String theRequestPath) {
StringTokenizer tok = new StringTokenizer(theRequestPath, "/");
String resourceName = null;
IdDt id = null;
String operation = null;
String compartment = null;
if (tok.hasMoreTokens()) {
resourceName = tok.nextToken();
if (partIsOperation(resourceName)) {
operation = resourceName;
resourceName = null;
}
}
theRequestDetails.setResourceName(resourceName);
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (partIsOperation(nextString)) {
operation = nextString;
} else {
id = new IdDt(resourceName, UrlUtil.unescape(nextString));
}
}
if (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (nextString.equals(Constants.PARAM_HISTORY)) {
if (tok.hasMoreTokens()) {
String versionString = tok.nextToken();
if (id == null) {
throw new InvalidRequestException("Don't know how to handle request path: " + theRequestPath);
}
id = new IdDt(resourceName, id.getIdPart(), UrlUtil.unescape(versionString));
} else {
operation = Constants.PARAM_HISTORY;
}
} else if (partIsOperation(nextString)) {
if (operation != null) {
throw new InvalidRequestException("URL Path contains two operations: " + theRequestPath);
}
operation = nextString;
} else {
compartment = nextString;
}
}
// Secondary is for things like ..../_tags/_delete
String secondaryOperation = null;
while (tok.hasMoreTokens()) {
String nextString = tok.nextToken();
if (operation == null) {
operation = nextString;
} else if (secondaryOperation == null) {
secondaryOperation = nextString;
} else {
throw new InvalidRequestException("URL path has unexpected token '" + nextString + "' at the end: " + theRequestPath);
}
}
theRequestDetails.setId(id);
theRequestDetails.setOperation(operation);
theRequestDetails.setSecondaryOperation(secondaryOperation);
theRequestDetails.setCompartmentName(compartment);
}
/** /**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
* called immediately before beginning initialization of the restful server's internal init. * called immediately before beginning initialization of the restful server's internal init.

View File

@ -394,8 +394,9 @@ public class RestfulServerUtils {
return retVal; return retVal;
} }
public static Integer extractCountParameter(HttpServletRequest theRequest) { public static Integer extractCountParameter(RequestDetails theRequest) {
return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT); String paramName = Constants.PARAM_COUNT;
return tryToExtractNamedParameter(theRequest, paramName);
} }
public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) { public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) {
@ -674,18 +675,18 @@ public class RestfulServerUtils {
} }
} }
static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) { // static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name); // String countString = theRequest.getParameter(name);
Integer count = null; // Integer count = null;
if (isNotBlank(countString)) { // if (isNotBlank(countString)) {
try { // try {
count = Integer.parseInt(countString); // count = Integer.parseInt(countString);
} catch (NumberFormatException e) { // } catch (NumberFormatException e) {
ourLog.debug("Failed to parse _count value '{}': {}", countString, e); // ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
} // }
} // }
return count; // return count;
} // }
public static void validateResourceListNotNull(List<? extends IBaseResource> theResourceList) { public static void validateResourceListNotNull(List<? extends IBaseResource> theResourceList) {
if (theResourceList == null) { if (theResourceList == null) {
@ -701,4 +702,17 @@ public class RestfulServerUtils {
} }
} }
public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
String[] retVal = theRequest.getParameters().get(theParamName);
if (retVal == null) {
return null;
}
try {
return Integer.parseInt(retVal[0]);
} catch (NumberFormatException e) {
ourLog.debug("Failed to parse {} value '{}': {}", new Object[] {theParamName, retVal[0], e});
return null;
}
}
} }

View File

@ -187,7 +187,7 @@ public class UrlUtil {
char nextChar = url.charAt(idx); char nextChar = url.charAt(idx);
boolean atEnd = (idx + 1) == url.length(); boolean atEnd = (idx + 1) == url.length();
if (nextChar == '?' || nextChar == '/' || atEnd) { if (nextChar == '?' || nextChar == '/' || atEnd) {
int endIdx = atEnd ? idx + 1 : idx; int endIdx = (atEnd && nextChar != '?') ? idx + 1 : idx;
String nextSubstring = url.substring(nextStart, endIdx); String nextSubstring = url.substring(nextStart, endIdx);
if (retVal.getResourceType() == null) { if (retVal.getResourceType() == null) {
retVal.setResourceType(nextSubstring); retVal.setResourceType(nextSubstring);

View File

@ -802,22 +802,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) { public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) {
SearchParameterMap paramMap = new SearchParameterMap(); SearchParameterMap paramMap = new SearchParameterMap();
List<NameValuePair> parameters; List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create(); ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
for (NameValuePair next : parameters) { for (NameValuePair next : parameters) {
@ -891,6 +876,26 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return paramMap; return paramMap;
} }
protected static List<NameValuePair> translateMatchUrl(String theMatchUrl) {
List<NameValuePair> parameters;
String matchUrl = theMatchUrl;
int questionMarkIndex = matchUrl.indexOf('?');
if (questionMarkIndex != -1) {
matchUrl = matchUrl.substring(questionMarkIndex + 1);
}
matchUrl = matchUrl.replace("|", "%7C");
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
matchUrl = matchUrl.replace("=>", "=%3E");
matchUrl = matchUrl.replace("=<", "=%3C");
if (matchUrl.contains(" ")) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
}
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
return parameters;
}
@Override @Override
public void registerDaoListener(IDaoListener theListener) { public void registerDaoListener(IDaoListener theListener) {
Validate.notNull(theListener, "theListener"); Validate.notNull(theListener, "theListener");

View File

@ -22,11 +22,10 @@ package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.emptyCollectionOf;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -34,6 +33,7 @@ import java.util.Set;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
import org.apache.http.NameValuePair;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
@ -44,10 +44,11 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import com.google.common.collect.ArrayListMultimap;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt;
@ -61,16 +62,19 @@ import ca.uhn.fhir.model.dstu2.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.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding;
import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding.ResourceOrDstu1Bundle;
import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
@ -103,8 +107,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
resp.addEntry().setResource(ooResp); resp.addEntry().setResource(ooResp);
/* /*
* For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others
* doesn't prevent others
*/ */
for (final Entry nextRequestEntry : theRequest.getEntry()) { for (final Entry nextRequestEntry : theRequest.getEntry()) {
@ -129,8 +132,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
Entry subResponseEntry = nextResponseBundle.getEntry().get(0); Entry subResponseEntry = nextResponseBundle.getEntry().get(0);
resp.addEntry(subResponseEntry); resp.addEntry(subResponseEntry);
/* /*
* If the individual entry didn't have a resource in its response, bring the sub-transaction's * If the individual entry didn't have a resource in its response, bring the sub-transaction's OperationOutcome across so the client can see it
* OperationOutcome across so the client can see it
*/ */
if (subResponseEntry.getResource() == null) { if (subResponseEntry.getResource() == null) {
subResponseEntry.setResource(nextResponseBundle.getEntry().get(0).getResource()); subResponseEntry.setResource(nextResponseBundle.getEntry().get(0).getResource());
@ -319,72 +321,67 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
} }
case GET: { case GET: {
// SEARCH/READ/VREAD // SEARCH/READ/VREAD
RequestDetails requestDetails = new RequestDetails();
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
String url = extractTransactionUrlOrThrowException(nextEntry, verb); String url = extractTransactionUrlOrThrowException(nextEntry, verb);
UrlParts parts = UrlUtil.parseUrl(url);
@SuppressWarnings("rawtypes") int qIndex = url.indexOf('?');
IFhirResourceDao dao = toDao(parts, verb.getCode(), url); ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
requestDetails.setParameters(new HashMap<String, String[]>());
String ifNoneMatch = nextEntry.getRequest().getIfNoneMatch(); if (qIndex != -1) {
if (isNotBlank(ifNoneMatch)) { String params = url.substring(qIndex);
ifNoneMatch = MethodUtil.parseETagValue(ifNoneMatch); List<NameValuePair> parameters = translateMatchUrl(params);
for (NameValuePair next : parameters) {
paramValues.put(next.getName(), next.getValue());
}
for (java.util.Map.Entry<String, Collection<String>> nextParamEntry : paramValues.asMap().entrySet()) {
String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
}
url = url.substring(0, qIndex);
} }
if (parts.getResourceId() != null && parts.getParams() == null) { requestDetails.setRequestPath(url);
/* requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());
* GET is a read
*/ theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
IResource found; BaseMethodBinding<?> method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url);
boolean notChanged = false; if (method == null) {
if (parts.getVersionId() != null) { throw new IllegalArgumentException("Unable to handle GET " + url);
if (isNotBlank(ifNoneMatch)) { }
throw new InvalidRequestException("Unable to perform vread on '" + url + "' with ifNoneMatch also set. Do not include a version in the URL to perform a conditional read.");
} if (isNotBlank(nextEntry.getRequest().getIfMatch())) {
found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId())); requestDetails.addHeader(Constants.HEADER_IF_MATCH, nextEntry.getRequest().getIfMatch());
} else { }
found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId())); if (isNotBlank(nextEntry.getRequest().getIfNoneExist())) {
if (isNotBlank(ifNoneMatch) && ifNoneMatch.equals(found.getId().getVersionIdPart())) { requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, nextEntry.getRequest().getIfNoneExist());
notChanged = true; }
if (isNotBlank(nextEntry.getRequest().getIfNoneMatch())) {
requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextEntry.getRequest().getIfNoneMatch());
}
if (method instanceof BaseResourceReturningMethodBinding) {
try {
ResourceOrDstu1Bundle responseData = ((BaseResourceReturningMethodBinding) method).invokeServer(theRequestDetails.getServer(), requestDetails, new byte[0]);
Entry newEntry = response.addEntry();
IBaseResource resource = responseData.getResource();
if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) {
resource = filterNestedBundle(requestDetails, resource);
} }
newEntry.setResource((IResource) resource);
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
} catch (NotModifiedException e) {
Entry newEntry = response.addEntry();
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
} }
Entry entry = response.addEntry(); } else {
if (notChanged == false) { throw new IllegalArgumentException("Unable to handle GET " + url);
entry.setResource(found);
}
EntryResponse resp = entry.getResponse();
resp.setLocation(found.getId().toUnqualified().getValue());
resp.setEtag(found.getId().getVersionIdPart());
if (!notChanged) {
resp.setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
} else {
resp.setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
}
} else if (parts.getParams() != null) {
/*
* GET is a search
*/
RuntimeResourceDefinition def = getContext().getResourceDefinition(dao.getResourceType());
SearchParameterMap params = translateMatchUrl(url, def);
IBundleProvider bundle = dao.search(params);
Integer count = params.getCount();
if (count == null) {
count = bundle.preferredPageSize();
}
IVersionSpecificBundleFactory bundleFactory = getContext().newBundleFactory();
Set<Include> includes = new HashSet<Include>();
EncodingEnum linkEncoding = null;
bundleFactory.initializeBundleFromBundleProvider(theRequestDetails.getServer(), bundle, linkEncoding, theRequestDetails.getFhirServerBase(), url, false, 0, count, null, ca.uhn.fhir.model.valueset.BundleTypeEnum.SEARCHSET, includes);
Entry newEntry = response.addEntry();
newEntry.setResource((IResource) bundleFactory.getResourceBundle());
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
} }
} }
} }
} }
FhirTerser terser = getContext().newTerser(); FhirTerser terser = getContext().newTerser();
@ -428,7 +425,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
IFhirResourceDao<?> resourceDao = getDao(nextEntry.getResource().getClass()); IFhirResourceDao<?> resourceDao = getDao(nextEntry.getResource().getClass());
Set<Long> val = resourceDao.processMatchUrl(matchUrl); Set<Long> val = resourceDao.processMatchUrl(matchUrl);
if (val.size() > 1) { if (val.size() > 1) {
throw new InvalidRequestException("Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); throw new InvalidRequestException(
"Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
} }
} }
} }
@ -454,6 +452,20 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
return response; return response;
} }
/**
* This method is called for nested bundles (e.g. if we received a transaction with an entry that
* was a GET search, this method is called on the bundle for the search result, that will be placed in the
* outer bundle). This method applies the _summary and _content parameters to the output of
* that bundle.
*
* TODO: This isn't the most efficient way of doing this.. hopefully we can come up with something better in the future.
*/
private IBaseResource filterNestedBundle(RequestDetails theRequestDetails, IBaseResource theResource) {
IParser p = getContext().newJsonParser();
RestfulServerUtils.configureResponseParser(theRequestDetails, p);
return p.parseResource(theResource.getClass(), p.encodeResourceToString(theResource));
}
private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) { private ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
RuntimeResourceDefinition resType; RuntimeResourceDefinition resType;
try { try {
@ -471,10 +483,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
throw new InvalidRequestException(msg); throw new InvalidRequestException(msg);
} }
if (theParts.getResourceId() == null && theParts.getParams() == null) { // if (theParts.getResourceId() == null && theParts.getParams() == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); // String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg); // throw new InvalidRequestException(msg);
} // }
return dao; return dao;
} }
@ -487,15 +499,15 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
return retVal; return retVal;
} }
private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry, String theResourceType, IResource theRes) { private static void handleTransactionCreateOrUpdateOutcome(Map<IdDt, IdDt> idSubstitutions, Map<IdDt, DaoMethodOutcome> idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome,
Entry newEntry, String theResourceType, IResource theRes) {
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless(); IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();
IdDt resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless(); IdDt resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) { if (newId.equals(resourceId) == false) {
idSubstitutions.put(resourceId, newId); idSubstitutions.put(resourceId, newId);
if (isPlaceholder(resourceId)) { if (isPlaceholder(resourceId)) {
/* /*
* The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient.
* kind too just to be lenient.
*/ */
idSubstitutions.put(new IdDt(theResourceType + '/' + resourceId.getValue()), newId); idSubstitutions.put(new IdDt(theResourceType + '/' + resourceId.getValue()), newId);
} }

View File

@ -60,7 +60,7 @@ public class BaseJpaProvider {
Enumeration<String> forwardedFors = theRequest.getHeaders("x-forwarded-for"); Enumeration<String> forwardedFors = theRequest.getHeaders("x-forwarded-for");
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
for (Enumeration<String> enums = forwardedFors; enums.hasMoreElements();) { for (Enumeration<String> enums = forwardedFors; enums != null && enums.hasMoreElements();) {
if (b.length() > 0) { if (b.length() > 0) {
b.append(" / "); b.append(" / ");
} }

View File

@ -22,8 +22,14 @@ import static org.mockito.Mockito.when;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.Before; import org.junit.Before;
@ -37,6 +43,7 @@ import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.TagList;
@ -63,6 +70,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
@ -74,6 +82,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2Test.class);
private RequestDetails myRequestDetails; private RequestDetails myRequestDetails;
private RestfulServer myServer;
@Test @Test
public void testTransactionFromBundle6() throws Exception { public void testTransactionFromBundle6() throws Exception {
@ -310,10 +319,25 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
} }
@SuppressWarnings("unchecked")
@Before @Before
public void before() { public void before() throws ServletException {
myRequestDetails = mock(RequestDetails.class); myRequestDetails = mock(RequestDetails.class);
when(myRequestDetails.getServer()).thenReturn(new RestfulServer());
if (myServer == null) {
myServer = new RestfulServer(myFhirCtx);
PatientResourceProvider patientRp = new PatientResourceProvider();
patientRp.setDao(myPatientDao);
myServer.setResourceProviders(patientRp);
myServer.init(mock(ServletConfig.class));
}
when(myRequestDetails.getServer()).thenReturn(myServer);
HttpServletRequest servletRequest = mock(HttpServletRequest.class);
when(myRequestDetails.getServletRequest()).thenReturn(servletRequest);
when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class));
when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("/Patient"));
} }
@Test @Test
@ -1029,8 +1053,8 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals("200 OK", nextEntry.getResponse().getStatus()); assertEquals("200 OK", nextEntry.getResponse().getStatus());
nextEntry = resp.getEntry().get(2); nextEntry = resp.getEntry().get(2);
assertNull(nextEntry.getResource());
assertEquals("304 Not Modified", nextEntry.getResponse().getStatus()); assertEquals("304 Not Modified", nextEntry.getResponse().getStatus());
assertNull(nextEntry.getResource());
} }
@Test @Test

View File

@ -2,7 +2,12 @@ package ca.uhn.fhir.jpa.provider;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*; import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -16,29 +21,22 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestDstu1Config; import ca.uhn.fhir.jpa.dao.BaseJpaDstu2Test;
import ca.uhn.fhir.jpa.config.TestDstu2Config;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.rp.dstu2.ObservationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.ObservationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu2.OrganizationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.OrganizationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
@ -46,36 +44,74 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import net.sf.saxon.lib.OutputURIResolver;
public class SystemProviderDstu2Test extends BaseJpaTest { public class SystemProviderDstu2Test extends BaseJpaDstu2Test {
private static RestfulServer myRestServer;
private static IGenericClient ourClient;
private static FhirContext ourCtx;
private static CloseableHttpClient ourHttpClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderDstu2Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SystemProviderDstu2Test.class);
private static Server ourServer; private static Server ourServer;
private static AnnotationConfigApplicationContext ourAppCtx;
private static FhirContext ourCtx;
private static IGenericClient ourClient;
private static String ourServerBase; private static String ourServerBase;
private static CloseableHttpClient ourHttpClient;
private static RestfulServer restServer;
@Test @Before
public void testEverythingType() throws Exception { public void beforeStartServer() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); if (myRestServer == null) {
CloseableHttpResponse http = ourHttpClient.execute(get); PatientResourceProvider patientRp = new PatientResourceProvider();
try { patientRp.setDao(myPatientDao);
assertEquals(200, http.getStatusLine().getStatusCode());
} finally { QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2();
http.close(); questionnaireRp.setDao(myQuestionnaireDao);
ObservationResourceProvider observationRp = new ObservationResourceProvider();
observationRp.setDao(myObservationDao);
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
organizationRp.setDao(myOrganizationDao);
RestfulServer restServer = new RestfulServer(ourCtx);
restServer.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(10));
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
restServer.setPlainProviders(mySystemProvider);
int myPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(myPort);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ourServerBase = "http://localhost:" + myPort + "/fhir/context";
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourCtx = FhirContext.forDstu2();
restServer.setFhirContext(ourCtx);
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourHttpClient = builder.build();
ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.setLogRequestAndResponse(true);
myRestServer = restServer;
} }
} }
@Test @Test
public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception { public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception {
restServer.setDefaultResponseEncoding(EncodingEnum.JSON); myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
restServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10)); myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10));
ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor();
restServer.registerInterceptor(interceptor); myRestServer.registerInterceptor(interceptor);
for (int i = 0; i < 11; i++) { for (int i = 0; i < 11; i++) {
Patient p = new Patient(); Patient p = new Patient();
@ -95,50 +131,24 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
http.close(); http.close();
} }
restServer.unregisterInterceptor(interceptor); myRestServer.unregisterInterceptor(interceptor);
} }
@Test @Test
public void testTransactionFromBundle4() throws Exception { public void testEverythingType() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle.xml"); HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
String bundle = IOUtils.toString(bundleRes); CloseableHttpResponse http = ourHttpClient.execute(get);
String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute();
ourLog.info(response);
Bundle bundleResp = ourCtx.newXmlParser().parseResource(Bundle.class, response);
IdDt id = new IdDt(bundleResp.getEntry().get(0).getResponse().getLocation());
assertEquals("Patient", id.getResourceType());
assertTrue(id.hasIdPart());
assertTrue(id.isIdPartValidLong());
assertTrue(id.hasVersionIdPart());
assertTrue(id.isVersionIdPartValidLong());
}
@Test
public void testTransactionFromBundle5() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle2.xml");
String bundle = IOUtils.toString(bundleRes);
try { try {
ourClient.transaction().withBundle(bundle).prettyPrint().execute(); assertEquals(200, http.getStatusLine().getStatusCode());
fail(); } finally {
} catch (InvalidRequestException e) { http.close();
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics());
assertEquals("processing", oo.getIssue().get(0).getCode());
} }
} }
@Test @Test
public void testTransactionFromBundle6() throws Exception { public void testGetOperationDefinition() {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml"); OperationDefinition op = ourClient.read(OperationDefinition.class, "get-resource-counts");
String bundle = IOUtils.toString(bundleRes); assertEquals("$get-resource-counts", op.getCode());
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
// try {
// fail();
// } catch (InvalidRequestException e) {
// OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
// assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics());
// assertEquals("processing", oo.getIssue().get(0).getCode());
// }
} }
@Test @Test
@ -149,24 +159,6 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
ourLog.info(response); ourLog.info(response);
} }
/**
* This is Gramahe's test transaction - it requires some set up in order to work
*/
// @Test
public void testTransactionFromBundle3() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/grahame-transaction.xml");
String bundle = IOUtils.toString(bundleRes);
String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute();
ourLog.info(response);
}
@Test
public void testGetOperationDefinition() {
OperationDefinition op = ourClient.read(OperationDefinition.class, "get-resource-counts");
assertEquals("$get-resource-counts", op.getCode());
}
@Test @Test
public void testTransactionFromBundle2() throws Exception { public void testTransactionFromBundle2() throws Exception {
@ -204,65 +196,108 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
assertEquals(id1_4.toVersionless(), id2_4.toVersionless()); assertEquals(id1_4.toVersionless(), id2_4.toVersionless());
} }
/**
* This is Gramahe's test transaction - it requires some set up in order to work
*/
// @Test
public void testTransactionFromBundle3() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/grahame-transaction.xml");
String bundle = IOUtils.toString(bundleRes);
String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute();
ourLog.info(response);
}
@Test
public void testTransactionFromBundle4() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle.xml");
String bundle = IOUtils.toString(bundleRes);
String response = ourClient.transaction().withBundle(bundle).prettyPrint().execute();
ourLog.info(response);
Bundle bundleResp = ourCtx.newXmlParser().parseResource(Bundle.class, response);
IdDt id = new IdDt(bundleResp.getEntry().get(0).getResponse().getLocation());
assertEquals("Patient", id.getResourceType());
assertTrue(id.hasIdPart());
assertTrue(id.isIdPartValidLong());
assertTrue(id.hasVersionIdPart());
assertTrue(id.isVersionIdPartValidLong());
}
@Test
public void testTransactionFromBundle5() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle2.xml");
String bundle = IOUtils.toString(bundleRes);
try {
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
fail();
} catch (InvalidRequestException e) {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics());
assertEquals("processing", oo.getIssue().get(0).getCode());
}
}
@Test
public void testTransactionFromBundle6() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml");
String bundle = IOUtils.toString(bundleRes);
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
// try {
// fail();
// } catch (InvalidRequestException e) {
// OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
// assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics());
// assertEquals("processing", oo.getIssue().get(0).getCode());
// }
}
@Test
public void testTransactionSearch() throws Exception {
for (int i = 0; i < 20; i ++) {
Patient p = new Patient();
p.addName().addFamily("PATIENT_" + i);
myPatientDao.create(p);
}
Bundle req = new Bundle();
req.setType(BundleTypeEnum.TRANSACTION);
req.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient?");
Bundle resp = ourClient.transaction().withBundle(req).execute();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
assertEquals(1, resp.getEntry().size());
Bundle respSub = (Bundle)resp.getEntry().get(0).getResource();
assertEquals("self", respSub.getLink().get(0).getRelation());
assertEquals(ourServerBase + "/Patient", respSub.getLink().get(0).getUrl());
assertEquals("next", respSub.getLink().get(1).getRelation());
assertThat(respSub.getLink().get(1).getUrl(), containsString("/fhir/context?_getpages"));
assertThat(respSub.getEntry().get(0).getFullUrl(), startsWith(ourServerBase + "/Patient/"));
assertEquals(Patient.class, respSub.getEntry().get(0).getResource().getClass());
}
@Test
public void testTransactionCount() throws Exception {
for (int i = 0; i < 20; i ++) {
Patient p = new Patient();
p.addName().addFamily("PATIENT_" + i);
myPatientDao.create(p);
}
Bundle req = new Bundle();
req.setType(BundleTypeEnum.TRANSACTION);
req.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient?_summary=count");
Bundle resp = ourClient.transaction().withBundle(req).execute();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
assertEquals(1, resp.getEntry().size());
Bundle respSub = (Bundle)resp.getEntry().get(0).getResource();
assertEquals(20, respSub.getTotal().intValue());
assertEquals(0, respSub.getEntry().size());
}
@AfterClass @AfterClass
public static void afterClass() throws Exception { public static void afterClass() throws Exception {
ourServer.stop(); ourServer.stop();
ourAppCtx.stop();
}
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() throws Exception {
ourAppCtx = new AnnotationConfigApplicationContext(TestDstu2Config.class);
IFhirResourceDao<Patient> patientDao = (IFhirResourceDao<Patient>) ourAppCtx.getBean("myPatientDaoDstu2", IFhirResourceDao.class);
PatientResourceProvider patientRp = new PatientResourceProvider();
patientRp.setDao(patientDao);
IFhirResourceDao<Questionnaire> questionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDaoDstu2", IFhirResourceDao.class);
QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2();
questionnaireRp.setDao(questionnaireDao);
IFhirResourceDao<Observation> observationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class);
ObservationResourceProvider observationRp = new ObservationResourceProvider();
observationRp.setDao(observationDao);
IFhirResourceDao<Organization> organizationDao = (IFhirResourceDao<Organization>) ourAppCtx.getBean("myOrganizationDaoDstu2", IFhirResourceDao.class);
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
organizationRp.setDao(organizationDao);
restServer = new RestfulServer(ourCtx);
restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp);
JpaSystemProviderDstu2 systemProv = ourAppCtx.getBean(JpaSystemProviderDstu2.class, "mySystemProviderDstu2");
restServer.setPlainProviders(systemProv);
int myPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(myPort);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ourServerBase = "http://localhost:" + myPort + "/fhir/context";
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourCtx = FhirContext.forDstu2();
restServer.setFhirContext(ourCtx);
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourHttpClient = builder.build();
ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.setLogRequestAndResponse(true);
} }
} }

View File

@ -1,76 +0,0 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:annotation-config />
<context:mbean-server />
<bean depends-on="dbServer" id="myPersistenceDataSourceDstu1" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<!-- ;create=true /opt/glassfish/glassfish4/glassfish/nodes/localhost-domain1/fhirtest/fhirdb -->
<!-- <property name="url" value="jdbc:hsqldb:hsql://localhost/uhnfhirdb"/>-->
<!-- <property name="url" value="jdbc:derby:directory:#{systemproperties['fhir.db.location']};create=true" /> -->
<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
<property name="url" value="jdbc:derby://localhost:1527/#{systemProperties['fhir.db.location']};create=true" />
<!-- <property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" /> -->
<!--<property name="url" value="jdbc:derby://localhost:1527#{systemProperties['fhir.db.location']};create=true" />-->
<property name="username" value="SA"/>
<property name="password" value="SA"/>
</bean>
<bean depends-on="dbServer" id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myPersistenceDataSourceDstu1" />
<!--
<property name="persistenceXmlLocation" value="classpath:META-INF/fhirtest_persistence.xml" />
-->
<property name="packagesToScan">
<list>
<value>ca.uhn.fhir.jpa.entity</value>
</list>
</property>
<property name="persistenceUnitName" value="FHIR_DSTU2" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value="ca.uhn.fhir.jpa.util.HapiDerbyTenSevenDialect" />
<entry key="hibernate.hbm2ddl.auto" value="update" />
<entry key="hibernate.jdbc.batch_size" value="20" />
<entry key="hibernate.cache.use_minimal_puts" value="true" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.cache.use_query_cache" value="false" />
<entry key="hibernate.cache.use_second_level_cache" value="false" />
<entry key="hibernate.cache.use_structured_entries" value="false" />
</map>
</property>
</bean>
<!--for mysql-->
<!--
<bean depends-on="dbServer" id="myPersistenceDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://fhirdb.url/fhirdbname" />
<property name="username" value="sa"/>
<property name="password" value="sa"/>
<property name="testOnBorrow" value="true"/>
<property name="validationQuery" value="select 1;"/>
</bean>
-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

View File

@ -1,69 +0,0 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:annotation-config />
<context:mbean-server />
<bean depends-on="dbServer" id="myPersistenceDataSourceDstu2" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
<property name="url" value="jdbc:derby://localhost:1527/#{systemProperties['fhir.db.location.dstu2']};create=true" />
<property name="username" value="SA" />
<property name="password" value="SA" />
</bean>
<!--for mysql -->
<!-- <bean depends-on="dbServer" id="myPersistenceDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName"
value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://fhirdb.url/fhirdbname" /> <property name="username" value="sa"/> <property name="password"
value="sa"/> <property name="testOnBorrow" value="true"/> <property name="validationQuery" value="select 1;"/> </bean> -->
<bean depends-on="dbServer" id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myPersistenceDataSourceDstu2" />
<!--
<property name="persistenceXmlLocation" value="classpath:META-INF/fhirtest_persistence.xml" />
-->
<property name="persistenceUnitName" value="FHIR_DSTU2" />
<property name="packagesToScan">
<list>
<value>ca.uhn.fhir.jpa.entity</value>
</list>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" />
<!-- <property name="databasePlatform" value="ca.uhn.fhir.jpa.util.HapiDerbyTenSevenDialect" />-->
<!-- <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" /> -->
<!-- <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect" /> -->
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.dialect" value="ca.uhn.fhir.jpa.util.HapiDerbyTenSevenDialect" />
<entry key="hibernate.hbm2ddl.auto" value="update" />
<entry key="hibernate.jdbc.batch_size" value="20" />
<entry key="hibernate.cache.use_minimal_puts" value="true" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.cache.use_query_cache" value="false" />
<entry key="hibernate.cache.use_second_level_cache" value="false" />
<entry key="hibernate.cache.use_structured_entries" value="false" />
<entry key="hibernate.search.default.directory_provider" value="filesystem" />
<entry key="hibernate.search.default.indexBase" value="#{systemProperties['fhir.lucene.location.dstu2']}" />
<entry key="hibernate.search.lucene_version" value="LUCENE_CURRENT" />
</map>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>

View File

@ -11,7 +11,7 @@
</button> </button>
<a class="navbar-brand" <a class="navbar-brand"
th:href="'home?encoding=' + ${encoding} + '&amp;pretty=' + ${pretty}"> th:href="'home?encoding=' + ${encoding} + '&amp;pretty=' + ${pretty}">
<i class="fa fa-home topbarIcon" /> HAPI FHIR <i class="fa fa-home topbarIcon" /> Home
</a> </a>
<a class="navbar-left navbarBreadcrumb hidden-xs hidden-sm" <a class="navbar-left navbarBreadcrumb hidden-xs hidden-sm"
th:if="${resourceName} != null and ${resourceName.empty} == false" th:if="${resourceName} != null and ${resourceName.empty} == false"

View File

@ -21,7 +21,7 @@
<li>Jetty (JPA, CLI, Public Server): 9.2.6.v20141205 -&gt; 9.3.4.v20151007 </li> <li>Jetty (JPA, CLI, Public Server): 9.2.6.v20141205 -&gt; 9.3.4.v20151007 </li>
</ul> </ul>
]]> ]]>
</action> </action>
<action type="add"> <action type="add">
JPA and Tester Overlay now use Spring Java config files instead JPA and Tester Overlay now use Spring Java config files instead
of the older XML config files. All example projects have been updated. of the older XML config files. All example projects have been updated.
@ -183,6 +183,11 @@
Fix a crash when encoding a Binary resource in JSON encoding Fix a crash when encoding a Binary resource in JSON encoding
if the resource has no content-type if the resource has no content-type
</action> </action>
<action type="fix">
JPA server now supports read/history/search in transaction entries
by calling the actual implementing method in the server (previously
the call was simulated, which meant that many features did not work)
</action>
</release> </release>
<release version="1.2" date="2015-09-18"> <release version="1.2" date="2015-09-18">
<action type="add"> <action type="add">