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>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<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.UrlUtil;
abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> {
public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> {
protected static final Set<String> ALLOWED_PARAMS;
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);
if (collectionType != null) {
if (!Object.class.equals(collectionType) && !IBaseResource.class.isAssignableFrom(collectionType)) {
throw new ConfigurationException("Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: "
+ collectionType);
throw new ConfigurationException(
"Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: " + collectionType);
}
}
myResourceListCollectionType = collectionType;
@ -121,8 +121,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} else if (MethodOutcome.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.METHOD_OUTCOME;
} else {
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
+ theMethod.getDeclaringClass().getCanonicalName());
throw new ConfigurationException(
"Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
}
if (theReturnResourceType != null) {
@ -233,29 +233,52 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
throw new IllegalStateException("Should not get here!");
}
public abstract Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
@Override
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);
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
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
@ -267,9 +290,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
Object resultObj = invokeServer(theServer, theRequest, params);
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
boolean respondGzip = theRequest.isRespondGzip();
HttpServletResponse response = theRequest.getServletResponse();
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
final ResourceOrDstu1Bundle responseObject;
switch (getReturnType()) {
case BUNDLE: {
@ -323,17 +347,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
bundleFactory.initializeWithBundleResource(resource);
bundleFactory.addRootPropertiesToBundle(null, theRequest.getFhirServerBase(), linkSelf, count, getResponseBundleType(), lastUpdated);
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) {
ourLog.debug("Interceptor {} returned false, not continuing processing");
return;
}
}
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip,
isAddContentLocationHeader(), theRequest);
responseObject = new ResourceOrDstu1Bundle(resource);
break;
} else {
Set<Include> includes = getRequestIncludesFromParams(params);
@ -344,32 +358,18 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), theServer.getDefaultResponseEncoding());
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();
if (bundle != null) {
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
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);
responseObject = new ResourceOrDstu1Bundle(bundle);
} else {
IBaseResource resBundle = bundleFactory.getResourceBundle();
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
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);
responseObject = new ResourceOrDstu1Bundle(resBundle);
}
break;
@ -384,22 +384,17 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
IBaseResource resource = result.getResources(0, 1).get(0);
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);
responseObject = new ResourceOrDstu1Bundle(resource);
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
*/
@ -412,7 +407,32 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
}
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 {

View File

@ -214,7 +214,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
IBundleProvider retVal = toResourceList(response);
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)) {
List<IBaseResource> responseResources = retVal.getResources(0, 1);
IBaseResource responseResource = responseResources.get(0);

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.rest.server.RestfulServer;
public class RequestDetails {
private Map<String, ArrayList<String>> myHeaders = new HashMap<String, ArrayList<String>>();
private String myCompartmentName;
private String myCompleteUrl;
private String myFhirServerBase;
@ -230,4 +231,22 @@ public class RequestDetails {
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.InvalidRequestException;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
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.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -477,14 +478,14 @@ public class RestfulServer extends HttpServlet {
return;
}
Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest());
Integer count = RestfulServerUtils.extractCountParameter(theRequest);
if (count == null) {
count = getPagingProvider().getDefaultPageSize();
} else if (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) {
offsetI = 0;
}
@ -545,6 +546,12 @@ public class RestfulServer extends HttpServlet {
requestDetails.setRequestType(theRequestType);
requestDetails.setServletRequest(theRequest);
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 {
@ -556,7 +563,6 @@ public class RestfulServer extends HttpServlet {
}
}
String resourceName = null;
String requestFullPath = StringUtils.defaultString(theRequest.getRequestURI());
String servletPath = StringUtils.defaultString(theRequest.getServletPath());
StringBuffer requestUrl = theRequest.getRequestURL();
@ -572,11 +578,9 @@ public class RestfulServer extends HttpServlet {
ourLog.trace("Context Path: {}", servletContextPath);
}
IdDt id = null;
String operation = null;
String compartment = null;
String requestPath = getRequestPath(requestFullPath, servletContextPath, servletPath);
if (requestPath.length() > 0 && requestPath.charAt(0) == '/') {
requestPath = requestPath.substring(1);
}
@ -588,84 +592,16 @@ public class RestfulServer extends HttpServlet {
Map<String, String[]> params = new HashMap<String, String[]>(theRequest.getParameterMap());
requestDetails.setParameters(params);
StringTokenizer tok = new StringTokenizer(requestPath, "/");
if (tok.hasMoreTokens()) {
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);
}
}
IdDt id;
populateRequestDetailsFromRequestPath(requestDetails, requestPath);
if (theRequestType == RequestTypeEnum.PUT) {
String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION);
if (contentLocation != null) {
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);
boolean respondGzip = false;
@ -696,18 +632,7 @@ public class RestfulServer extends HttpServlet {
return;
}
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", theRequestType.name(), requestPath, params.keySet()));
}
}
BaseMethodBinding<?> resourceMethod = determineResourceMethod(requestDetails, requestPath);
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
* called immediately before beginning initialization of the restful server's internal init.

View File

@ -394,8 +394,9 @@ public class RestfulServerUtils {
return retVal;
}
public static Integer extractCountParameter(HttpServletRequest theRequest) {
return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT);
public static Integer extractCountParameter(RequestDetails theRequest) {
String paramName = Constants.PARAM_COUNT;
return tryToExtractNamedParameter(theRequest, paramName);
}
public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) {
@ -674,18 +675,18 @@ public class RestfulServerUtils {
}
}
static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name);
Integer count = null;
if (isNotBlank(countString)) {
try {
count = Integer.parseInt(countString);
} catch (NumberFormatException e) {
ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
}
}
return count;
}
// static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
// String countString = theRequest.getParameter(name);
// Integer count = null;
// if (isNotBlank(countString)) {
// try {
// count = Integer.parseInt(countString);
// } catch (NumberFormatException e) {
// ourLog.debug("Failed to parse _count value '{}': {}", countString, e);
// }
// }
// return count;
// }
public static void validateResourceListNotNull(List<? extends IBaseResource> theResourceList) {
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);
boolean atEnd = (idx + 1) == url.length();
if (nextChar == '?' || nextChar == '/' || atEnd) {
int endIdx = atEnd ? idx + 1 : idx;
int endIdx = (atEnd && nextChar != '?') ? idx + 1 : idx;
String nextSubstring = url.substring(nextStart, endIdx);
if (retVal.getResourceType() == null) {
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) {
SearchParameterMap paramMap = new SearchParameterMap();
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, '&');
List<NameValuePair> parameters = translateMatchUrl(theMatchUrl);
ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
for (NameValuePair next : parameters) {
@ -891,6 +876,26 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
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
public void registerDaoListener(IDaoListener 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.isBlank;
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.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -34,6 +33,7 @@ import java.util.Set;
import javax.persistence.TypedQuery;
import org.apache.http.NameValuePair;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
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.TransactionTemplate;
import com.google.common.collect.ArrayListMultimap;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.entity.TagDefinition;
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.base.composite.BaseResourceReferenceDt;
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.InstantDt;
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.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.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
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.util.FhirTerser;
import ca.uhn.fhir.util.UrlUtil;
@ -103,8 +107,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
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
* doesn't prevent others
* 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
*/
for (final Entry nextRequestEntry : theRequest.getEntry()) {
@ -129,8 +132,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
Entry subResponseEntry = nextResponseBundle.getEntry().get(0);
resp.addEntry(subResponseEntry);
/*
* 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
* 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
*/
if (subResponseEntry.getResource() == null) {
subResponseEntry.setResource(nextResponseBundle.getEntry().get(0).getResource());
@ -319,72 +321,67 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
}
case GET: {
// SEARCH/READ/VREAD
RequestDetails requestDetails = new RequestDetails();
requestDetails.setServletRequest(theRequestDetails.getServletRequest());
requestDetails.setRequestType(RequestTypeEnum.GET);
requestDetails.setServer(theRequestDetails.getServer());
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
UrlParts parts = UrlUtil.parseUrl(url);
@SuppressWarnings("rawtypes")
IFhirResourceDao dao = toDao(parts, verb.getCode(), url);
String ifNoneMatch = nextEntry.getRequest().getIfNoneMatch();
if (isNotBlank(ifNoneMatch)) {
ifNoneMatch = MethodUtil.parseETagValue(ifNoneMatch);
int qIndex = url.indexOf('?');
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
requestDetails.setParameters(new HashMap<String, String[]>());
if (qIndex != -1) {
String params = url.substring(qIndex);
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) {
/*
* GET is a read
*/
IResource found;
boolean notChanged = false;
if (parts.getVersionId() != null) {
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.");
}
found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId()));
} else {
found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId()));
if (isNotBlank(ifNoneMatch) && ifNoneMatch.equals(found.getId().getVersionIdPart())) {
notChanged = true;
requestDetails.setRequestPath(url);
requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
BaseMethodBinding<?> method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url);
if (method == null) {
throw new IllegalArgumentException("Unable to handle GET " + url);
}
if (isNotBlank(nextEntry.getRequest().getIfMatch())) {
requestDetails.addHeader(Constants.HEADER_IF_MATCH, nextEntry.getRequest().getIfMatch());
}
if (isNotBlank(nextEntry.getRequest().getIfNoneExist())) {
requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, nextEntry.getRequest().getIfNoneExist());
}
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();
if (notChanged == false) {
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));
} else {
throw new IllegalArgumentException("Unable to handle GET " + url);
}
}
}
}
FhirTerser terser = getContext().newTerser();
@ -428,7 +425,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
IFhirResourceDao<?> resourceDao = getDao(nextEntry.getResource().getClass());
Set<Long> val = resourceDao.processMatchUrl(matchUrl);
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;
}
/**
* 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) {
RuntimeResourceDefinition resType;
try {
@ -471,10 +483,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
throw new InvalidRequestException(msg);
}
if (theParts.getResourceId() == null && theParts.getParams() == null) {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
throw new InvalidRequestException(msg);
}
// if (theParts.getResourceId() == null && theParts.getParams() == null) {
// String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
// throw new InvalidRequestException(msg);
// }
return dao;
}
@ -487,15 +499,15 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
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 resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) {
idSubstitutions.put(resourceId, newId);
if (isPlaceholder(resourceId)) {
/*
* 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.
* 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.
*/
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");
StringBuilder b = new StringBuilder();
for (Enumeration<String> enums = forwardedFors; enums.hasMoreElements();) {
for (Enumeration<String> enums = forwardedFors; enums != null && enums.hasMoreElements();) {
if (b.length() > 0) {
b.append(" / ");
}

View File

@ -22,8 +22,14 @@ import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
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.hl7.fhir.instance.model.api.IIdType;
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.TagTypeEnum;
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.ResourceMetadataKeyEnum;
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.server.Constants;
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.exceptions.InvalidRequestException;
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 RequestDetails myRequestDetails;
private RestfulServer myServer;
@Test
public void testTransactionFromBundle6() throws Exception {
@ -310,10 +319,25 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
}
@SuppressWarnings("unchecked")
@Before
public void before() {
public void before() throws ServletException {
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
@ -1029,8 +1053,8 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals("200 OK", nextEntry.getResponse().getStatus());
nextEntry = resp.getEntry().get(2);
assertNull(nextEntry.getResource());
assertEquals("304 Not Modified", nextEntry.getResponse().getStatus());
assertNull(nextEntry.getResource());
}
@Test

View File

@ -2,7 +2,12 @@ package ca.uhn.fhir.jpa.provider;
import static org.hamcrest.Matchers.containsString;
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.util.concurrent.TimeUnit;
@ -16,29 +21,22 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Before;
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.jpa.config.TestDstu1Config;
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.dao.BaseJpaDstu2Test;
import ca.uhn.fhir.jpa.rp.dstu2.ObservationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu2.OrganizationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider;
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.OperationDefinition;
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.rest.client.IGenericClient;
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.exceptions.InvalidRequestException;
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 Server ourServer;
private static AnnotationConfigApplicationContext ourAppCtx;
private static FhirContext ourCtx;
private static IGenericClient ourClient;
private static String ourServerBase;
private static CloseableHttpClient ourHttpClient;
private static RestfulServer restServer;
@Test
public void testEverythingType() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
assertEquals(200, http.getStatusLine().getStatusCode());
} finally {
http.close();
@Before
public void beforeStartServer() throws Exception {
if (myRestServer == null) {
PatientResourceProvider patientRp = new PatientResourceProvider();
patientRp.setDao(myPatientDao);
QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2();
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
public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception {
restServer.setDefaultResponseEncoding(EncodingEnum.JSON);
restServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10));
myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON);
myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10));
ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor();
restServer.registerInterceptor(interceptor);
myRestServer.registerInterceptor(interceptor);
for (int i = 0; i < 11; i++) {
Patient p = new Patient();
@ -95,50 +131,24 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
http.close();
}
restServer.unregisterInterceptor(interceptor);
myRestServer.unregisterInterceptor(interceptor);
}
@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);
public void testEverythingType() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
CloseableHttpResponse http = ourHttpClient.execute(get);
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());
assertEquals(200, http.getStatusLine().getStatusCode());
} finally {
http.close();
}
}
@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());
// }
public void testGetOperationDefinition() {
OperationDefinition op = ourClient.read(OperationDefinition.class, "get-resource-counts");
assertEquals("$get-resource-counts", op.getCode());
}
@Test
@ -149,24 +159,6 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
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
public void testTransactionFromBundle2() throws Exception {
@ -204,65 +196,108 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
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
public static void afterClass() throws Exception {
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>
<a class="navbar-brand"
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 class="navbar-left navbarBreadcrumb hidden-xs hidden-sm"
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>
</ul>
]]>
</action>
</action>
<action type="add">
JPA and Tester Overlay now use Spring Java config files instead
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
if the resource has no content-type
</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 version="1.2" date="2015-09-18">
<action type="add">