Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2015-10-21 07:58:04 -04:00
commit e9653cf6aa
39 changed files with 1012 additions and 678 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

@ -1,5 +1,25 @@
package ca.uhn.fhir.context;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;

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

@ -388,6 +388,8 @@ public class MethodUtil {
}
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
param = new ServletRequestParameter();
} else if (parameterType.equals(RequestDetails.class)) {
param = new RequestDetailsParameter();
} else if (parameterType.equals(SummaryEnum.class)) {
param = new SummaryEnumParameter();
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {

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

@ -0,0 +1,55 @@
package ca.uhn.fhir.rest.method;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class RequestDetailsParameter implements IParameter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RequestDetailsParameter.class);
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
/*
* Does nothing, since we just ignore HttpServletRequest arguments
*/
ourLog.trace("Ignoring RequestDetailsParameter argument: {}", theSourceClientArgument);
}
@Override
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
return theRequest;
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// ignore
}
}

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

@ -1,5 +1,25 @@
package ca.uhn.fhir.validation.schematron;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Constructor;
import ca.uhn.fhir.context.FhirContext;

View File

@ -8,6 +8,7 @@ import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketFrame;
@ -100,8 +101,12 @@ public class WebsocketSubscribeCommand extends BaseCommand {
@OnWebSocketFrame
public void onFrame(Frame theFrame) {
ourLog.info("Websocket frame: {}", theFrame);
myQuit = true;
ourLog.debug("Websocket frame: {}", theFrame);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
ourLog.info("Received CLOSE status={} reason={}", statusCode, reason);
}
@OnWebSocketConnect

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.config;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.config;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.List;
import org.springframework.beans.factory.annotation.Autowire;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.config;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.config;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;

View File

@ -44,6 +44,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
@ -59,7 +60,7 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao<List<IResource>> {
@Transactional(propagation = Propagation.REQUIRED)
@Override
public List<IResource> transaction(List<IResource> theResources) {
public List<IResource> transaction(RequestDetails theRequestDetails, List<IResource> theResources) {
ourLog.info("Beginning transaction with {} resources", theResources.size());
// Notify interceptors

View File

@ -23,6 +23,7 @@ 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 java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
@ -32,15 +33,19 @@ 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;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
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;
@ -57,13 +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.IBundleProvider;
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;
@ -83,12 +94,12 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
return url;
}
private Bundle batch(Bundle theRequest) {
private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
ourLog.info("Beginning batch with {} resources", theRequest.getEntry().size());
long start = System.currentTimeMillis();
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Bundle resp = new Bundle();
resp.setType(BundleTypeEnum.BATCH_RESPONSE);
@ -96,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()) {
@ -109,7 +119,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
subRequestBundle.setType(BundleTypeEnum.TRANSACTION);
subRequestBundle.addEntry(nextRequestEntry);
Bundle subResponseBundle = transaction(subRequestBundle, "Batch sub-request");
Bundle subResponseBundle = transaction(theRequestDetails, subRequestBundle, "Batch sub-request");
return subResponseBundle;
}
};
@ -122,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());
@ -173,19 +182,19 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao<Bundle> {
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(Bundle theRequest) {
public Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest) {
ActionRequestDetails requestDetails = new ActionRequestDetails(null, "Bundle", theRequest);
notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);
String theActionName = "Transaction";
return transaction(theRequest, theActionName);
String actionName = "Transaction";
return transaction(theRequestDetails, theRequest, actionName);
}
@SuppressWarnings("unchecked")
private Bundle transaction(Bundle theRequest, String theActionName) {
private Bundle transaction(RequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
BundleTypeEnum transactionType = theRequest.getTypeElement().getValueAsEnum();
if (transactionType == BundleTypeEnum.BATCH) {
return batch(theRequest);
return batch(theRequestDetails, theRequest);
}
if (transactionType == null) {
@ -312,67 +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) {
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) {
RuntimeResourceDefinition def = getContext().getResourceDefinition(dao.getResourceType());
SearchParameterMap params = translateMatchUrl(url, def);
IBundleProvider bundle = dao.search(params);
Bundle searchBundle = new Bundle();
searchBundle.setTotal(bundle.size());
int configuredMax = 200; // this should probably be configurable or something
if (bundle.size() > configuredMax) {
throw new InvalidRequestException("Search nested within transaction found more than " + configuredMax + " matches, but paging is not supported in nested transactions");
}
List<IBaseResource> resourcesToAdd = bundle.getResources(0, Math.min(bundle.size(), configuredMax));
for (IBaseResource next : resourcesToAdd) {
searchBundle.addEntry().setResource((IResource) next);
}
Entry newEntry = response.addEntry();
newEntry.setResource(searchBundle);
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
} else {
throw new IllegalArgumentException("Unable to handle GET " + url);
}
}
}
}
FhirTerser terser = getContext().newTerser();
@ -416,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?");
}
}
}
@ -442,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 {
@ -459,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;
}
@ -475,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

@ -25,6 +25,7 @@ import java.util.Map;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.IBundleProvider;
/**
@ -58,6 +59,6 @@ public interface IFhirSystemDao<T> extends IDao {
*/
MetaDt metaGetOperation();
T transaction(T theResources);
T transaction(RequestDetails theRequestDetails, T theResources);
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.List;
public interface ISearchDao {

View File

@ -29,12 +29,23 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.jboss.logging.MDC;
import ca.uhn.fhir.rest.method.RequestDetails;
public class BaseJpaProvider {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaProvider.class);
public static final String REMOTE_ADDR = "req.remoteAddr";
public static final String REMOTE_UA = "req.userAgent";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaProvider.class);
public void endRequest(HttpServletRequest theRequest) {
MDC.remove(REMOTE_ADDR);
MDC.remove(REMOTE_UA);
}
public void endRequest(RequestDetails theRequest) {
endRequest(theRequest.getServletRequest());
}
public void startRequest(HttpServletRequest theRequest) {
if (theRequest == null) {
@ -49,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(" / ");
}
@ -71,9 +82,8 @@ public class BaseJpaProvider {
}
public void endRequest(HttpServletRequest theRequest) {
MDC.remove(REMOTE_ADDR);
MDC.remove(REMOTE_UA);
public void startRequest(RequestDetails theRequest) {
startRequest(theRequest.getServletRequest());
}
}

View File

@ -22,21 +22,20 @@ package ca.uhn.fhir.jpa.provider;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.method.RequestDetails;
public class JpaSystemProviderDstu1 extends BaseJpaSystemProvider<List<IResource>> {
@Transaction
public List<IResource> transaction(HttpServletRequest theRequest, @TransactionParam List<IResource> theResources) {
startRequest(theRequest);
public List<IResource> transaction(RequestDetails theRequestDetails, @TransactionParam List<IResource> theResources) {
startRequest(theRequestDetails);
try {
return getDao().transaction(theResources);
return getDao().transaction(theRequestDetails, theResources);
} finally {
endRequest(theRequest);
endRequest(theRequestDetails);
}
}

View File

@ -40,6 +40,7 @@ import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.TransactionParam;
import ca.uhn.fhir.rest.method.RequestDetails;
public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider<Bundle> {
@ -182,12 +183,12 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider<Bundle> {
}
@Transaction
public Bundle transaction(HttpServletRequest theRequest, @TransactionParam Bundle theResources) {
startRequest(theRequest);
public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
startRequest(theRequestDetails);
try {
return getDao().transaction(theResources);
return getDao().transaction(theRequestDetails, theResources);
} finally {
endRequest(theRequest);
endRequest(theRequestDetails);
}
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.search;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor;
import org.hibernate.search.indexes.interceptor.IndexingOverride;

View File

@ -218,7 +218,9 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement
if (!id.hasIdPart() || !id.isIdPartValid()) {
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
String message = "Invalid bind request - No ID included";
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e) {
handleFailure(e);
}
@ -236,7 +238,9 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement
myState = new BoundStaticSubscipriptionState(theSession);
} catch (ResourceNotFoundException e) {
try {
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - Unknown subscription: " + id.getValue()));
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
ourLog.warn(message);
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
} catch (IOException e1) {
handleFailure(e);
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.util;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.math.BigDecimal;
import org.apache.lucene.document.Document;

View File

@ -60,6 +60,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
extraProperties.put("hibernate.format_sql", "true");
extraProperties.put("hibernate.show_sql", "false");
extraProperties.put("hibernate.hbm2ddl.auto", "update");
extraProperties.put("hibernate.dialect", "org.hibernate.dialect.DerbyTenSevenDialect");
return extraProperties;
}

View File

@ -9,6 +9,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -21,10 +22,10 @@ import java.util.Map;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
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;
@ -41,6 +42,7 @@ import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -54,6 +56,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
private static IFhirResourceDao<Observation> ourObservationDao;
private static IFhirResourceDao<Patient> ourPatientDao;
private static IFhirSystemDao<List<IResource>> ourSystemDao;
private RequestDetails myRequestDetails;
@Test
public void testGetResourceCounts() {
@ -140,7 +143,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01");
obs.setSubject(new ResourceReferenceDt("Patient/testPersistWithSimpleLinkP01"));
ourSystemDao.transaction(Arrays.asList((IResource) patient, obs));
ourSystemDao.transaction(myRequestDetails, Arrays.asList((IResource) patient, obs));
String patientId = (patient.getId().getIdPart());
String obsId = (obs.getId().getIdPart());
@ -169,7 +172,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02");
obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO02");
ourSystemDao.transaction(Arrays.asList((IResource) patient, obs));
ourSystemDao.transaction(myRequestDetails, Arrays.asList((IResource) patient, obs));
String patientId2 = (patient.getId().getIdPart());
String patientVersion2 = (patient.getId().getVersionIdPart());
@ -183,6 +186,11 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
}
@Before
public void before() {
myRequestDetails = mock(RequestDetails.class);
}
@Test
public void testPersistWithUnknownId() {
Observation obs = new Observation();
@ -190,7 +198,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
obs.setSubject(new ResourceReferenceDt("Patient/999998888888"));
try {
ourSystemDao.transaction(Arrays.asList((IResource) obs));
ourSystemDao.transaction(myRequestDetails, Arrays.asList((IResource) obs));
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Resource Patient/999998888888 not found, specified in path: Observation.subject"));
}
@ -200,7 +208,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
obs.setSubject(new ResourceReferenceDt("Patient/1.2.3.4"));
try {
ourSystemDao.transaction(Arrays.asList((IResource) obs));
ourSystemDao.transaction(myRequestDetails, Arrays.asList((IResource) obs));
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Resource Patient/1.2.3.4 not found, specified in path: Observation.subject"));
}
@ -290,7 +298,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
patient2.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds"));
patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02");
ourSystemDao.transaction(Arrays.asList((IResource) patient1, patient2));
ourSystemDao.transaction(myRequestDetails, Arrays.asList((IResource) patient1, patient2));
}
@Test
@ -300,7 +308,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
Bundle bundle = ourFhirContext.newXmlParser().parseBundle(new InputStreamReader(bundleRes));
List<IResource> res = bundle.toListOfResources();
ourSystemDao.transaction(res);
ourSystemDao.transaction(myRequestDetails, res);
Patient p1 = (Patient) res.get(0);
String id = p1.getId().getValue();
@ -333,7 +341,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
o2.setSubject(new ResourceReferenceDt("Patient/cid:patient1"));
res.add(o2);
ourSystemDao.transaction(res);
ourSystemDao.transaction(myRequestDetails, res);
assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$"));
assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$"));
@ -355,7 +363,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
res.add(next.getResource());
}
List<IResource> response = ourSystemDao.transaction(res);
List<IResource> response = ourSystemDao.transaction(myRequestDetails, res);
String encodeResourceToString = ourFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(response.get(0));
ourLog.info(encodeResourceToString);
@ -388,7 +396,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
o2.setSubject(new ResourceReferenceDt("cid:patient1"));
res.add(o2);
ourSystemDao.transaction(res);
ourSystemDao.transaction(myRequestDetails, res);
assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$"));
assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$"));
@ -421,7 +429,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
p3.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete");
res.add(p3);
ourSystemDao.transaction(res);
ourSystemDao.transaction(myRequestDetails, res);
/*
* Verify
@ -447,7 +455,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
ResourceMetadataKeyEnum.DELETED_AT.put(p2, InstantDt.withCurrentTime());
res.add(p2);
ourSystemDao.transaction(res);
ourSystemDao.transaction(myRequestDetails, res);
/*
* Verify

View File

@ -14,28 +14,36 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.FhirContext;
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;
@ -47,7 +55,6 @@ import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse;
import ca.uhn.fhir.model.dstu2.resource.Communication;
import ca.uhn.fhir.model.dstu2.resource.Medication;
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
import ca.uhn.fhir.model.dstu2.resource.Observation;
@ -58,11 +65,13 @@ import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
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;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
@ -72,15 +81,27 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetai
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 {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml");
String bundle = IOUtils.toString(bundleRes);
Bundle output = mySystemDao.transaction(myFhirCtx.newXmlParser().parseResource(Bundle.class, bundle));
Bundle output = mySystemDao.transaction(myRequestDetails, myFhirCtx.newXmlParser().parseResource(Bundle.class, bundle));
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
}
@Test
public void testNestedCount() {
Patient p = new Patient();
p.addName().addFamily("family");
final IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
}
@Test
public void testRendexing() {
Patient p = new Patient();
@ -262,7 +283,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o.getSubject().setReference("Patient/" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(2, resp.getEntry().size());
Entry respEntry = resp.getEntry().get(0);
@ -290,7 +311,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process transaction where incoming Bundle.type = searchset", e.getMessage());
@ -298,6 +319,27 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
}
@SuppressWarnings("unchecked")
@Before
public void before() throws ServletException {
myRequestDetails = mock(RequestDetails.class);
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
public void testTransactionSingleEmptyResource() {
@ -307,7 +349,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process transaction where incoming Bundle.type = searchset", e.getMessage());
@ -327,7 +369,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient/THIS_ID_DOESNT_EXIST");
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(3, resp.getEntry().size());
assertEquals(BundleTypeEnum.BATCH_RESPONSE, resp.getTypeElement().getValueAsEnum());
@ -377,7 +419,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(),
@ -397,7 +439,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
try {
entry.setIfNoneExist("Patient?identifier identifier" + methodName);
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Failed to parse match URL[Patient?identifier identifiertestTransactionCreateWithInvalidMatchUrl] - URL is invalid (must not contain spaces)", e.getMessage());
@ -405,7 +447,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
try {
entry.setIfNoneExist("Patient?identifier=");
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid match URL[Patient?identifier=] - URL has no search parameters", e.getMessage());
@ -413,7 +455,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
try {
entry.setIfNoneExist("Patient?foo=bar");
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Failed to parse match URL[Patient?foo=bar] - Resource type Patient does not have a parameter with name: foo", e.getMessage());
@ -435,7 +477,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(),
@ -470,7 +512,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (PreconditionFailedException e) {
assertThat(e.getMessage(), containsString("with match URL \"Patient"));
@ -493,7 +535,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o.getSubject().setReference("Patient/" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(BundleTypeEnum.TRANSACTION_RESPONSE, resp.getTypeElement().getValueAsEnum());
assertEquals(2, resp.getEntry().size());
@ -525,7 +567,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
p.setId("Patient/" + methodName);
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST).setIfNoneExist("Patient?identifier=urn%3Asystem%7C" + methodName);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(1, resp.getEntry().size());
Entry respEntry = resp.getEntry().get(0);
@ -564,7 +606,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Resource Organization/9999999999999999 not found, specified in path: Patient.managingOrganization"));
@ -583,7 +625,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Resource Organization/" + methodName + " not found, specified in path: Patient.managingOrganization"));
@ -613,7 +655,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.read(id1.toVersionless());
myPatientDao.read(id2.toVersionless());
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(2, resp.getEntry().size());
assertEquals("204 No Content", resp.getEntry().get(0).getResponse().getStatus());
@ -647,7 +689,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
Bundle request = new Bundle();
request.addEntry().getRequest().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(1, resp.getEntry().size());
Entry nextEntry = resp.getEntry().get(0);
@ -701,7 +743,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().getRequest().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (PreconditionFailedException e) {
assertThat(e.getMessage(), containsString("resource with match URL \"Patient?"));
@ -716,7 +758,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().getRequest().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName);
// try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
// fail();
// } catch (ResourceNotFoundException e) {
// assertThat(e.getMessage(), containsString("resource matching URL \"Patient?"));
@ -736,7 +778,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
Bundle request = new Bundle();
request.addEntry().getRequest().setMethod(HTTPVerbEnum.DELETE).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName);
Bundle res = mySystemDao.transaction(request);
Bundle res = mySystemDao.transaction(myRequestDetails, request);
assertEquals(1, res.getEntry().size());
assertEquals(Constants.STATUS_HTTP_204_NO_CONTENT + " No Content", res.getEntry().get(0).getResponse().getStatus());
@ -763,7 +805,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02");
request.addEntry().setResource(patient2).getRequest().setMethod(HTTPVerbEnum.POST);
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
}
@Test
@ -773,7 +815,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
String bundleStr = IOUtils.toString(bundleRes);
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, bundleStr);
Bundle resp = mySystemDao.transaction(bundle);
Bundle resp = mySystemDao.transaction(myRequestDetails, bundle);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
@ -792,7 +834,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
String bundleStr = IOUtils.toString(bundleRes);
Bundle bundle = myFhirCtx.newJsonParser().parseResource(Bundle.class, bundleStr);
Bundle resp = mySystemDao.transaction(bundle);
Bundle resp = mySystemDao.transaction(myRequestDetails, bundle);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp));
@ -817,7 +859,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
ourLog.info("Request:\n"+myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
Bundle outcome = mySystemDao.transaction(bundle);
Bundle outcome = mySystemDao.transaction(myRequestDetails, bundle);
ourLog.info("Response:\n"+myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
IdDt medId1 = new IdDt(outcome.getEntry().get(0).getResponse().getLocation());
@ -839,7 +881,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
mo.setMedication(new ResourceReferenceDt(medId));
bundle.addEntry().setResource(mo).setFullUrl(mo.getId().getValue()).getRequest().setMethod(HTTPVerbEnum.POST);
outcome = mySystemDao.transaction(bundle);
outcome = mySystemDao.transaction(myRequestDetails, bundle);
IdDt medId2 = new IdDt(outcome.getEntry().get(0).getResponse().getLocation());
IdDt medOrderId2 = new IdDt(outcome.getEntry().get(1).getResponse().getLocation());
@ -875,7 +917,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl(idv1.toUnqualified().getValue());
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient?identifier=urn%3Asystem%7C" + methodName);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(3, resp.getEntry().size());
@ -943,7 +985,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
Bundle request = new Bundle();
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient?" + Constants.PARAM_COUNT + "=1");
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(1, resp.getEntry().size());
@ -957,7 +999,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request = new Bundle();
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient?" + Constants.PARAM_COUNT + "=GKJGKJG");
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
} catch (InvalidRequestException e) {
assertEquals(e.getMessage(), ("Invalid _count value: GKJGKJG"));
}
@ -966,7 +1008,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request = new Bundle();
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl("Patient?" + Constants.PARAM_COUNT + "=");
respBundle = mySystemDao.transaction(request);
respBundle = mySystemDao.transaction(myRequestDetails, request);
assertThat(respBundle.getEntry().size(), greaterThan(0));
}
@ -992,7 +1034,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()).setIfNoneMatch("W/\"" + idv1.getVersionIdPart() + "\"");
request.addEntry().getRequest().setMethod(HTTPVerbEnum.GET).setUrl(idv1.toUnqualifiedVersionless().getValue()).setIfNoneMatch("W/\"" + idv2.getVersionIdPart() + "\"");
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(3, resp.getEntry().size());
@ -1011,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
@ -1036,7 +1078,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o.getSubject().setReference("Patient/" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(2, resp.getEntry().size());
Entry nextEntry = resp.getEntry().get(0);
@ -1083,7 +1125,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (PreconditionFailedException e) {
assertThat(e.getMessage(), containsString("with match URL \"Patient"));
@ -1110,7 +1152,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o.getSubject().setReference("Patient/" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(2, resp.getEntry().size());
Entry nextEntry = resp.getEntry().get(0);
@ -1150,7 +1192,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o.getSubject().setReference("Patient/" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerbEnum.POST);
Bundle resp = mySystemDao.transaction(request);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(2, resp.getEntry().size());
Entry nextEntry = resp.getEntry().get(0);
@ -1293,7 +1335,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o2.setSubject(new ResourceReferenceDt("urn:oid:0.1.2.3"));
res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerbEnum.POST).setUrl("Observation");
Bundle resp = mySystemDao.transaction(res);
Bundle resp = mySystemDao.transaction(myRequestDetails, res);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
@ -1336,7 +1378,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
o2.setSubject(new ResourceReferenceDt("Patient/urn:oid:0.1.2.3"));
res.addEntry().setResource(o2).getRequest().setMethod(HTTPVerbEnum.POST).setUrl("Observation");
Bundle resp = mySystemDao.transaction(res);
Bundle resp = mySystemDao.transaction(myRequestDetails, res);
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp));
@ -1355,22 +1397,4 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
}
static void doDeleteEverything(IFhirSystemDao<Bundle> systemDao) {
IBundleProvider all = systemDao.history(null);
List<IBaseResource> allRes = all.getResources(0, all.size());
for (IBaseResource iResource : allRes) {
if (ResourceMetadataKeyEnum.DELETED_AT.get((IResource) iResource) == null) {
ourLog.info("Deleting: {}", iResource.getIdElement());
Bundle b = new Bundle();
b.setType(BundleTypeEnum.TRANSACTION);
String url = iResource.getIdElement().toVersionless().getValue();
b.addEntry().getRequest().setMethod(HTTPVerbEnum.DELETE).setUrl(url);
systemDao.transaction(b);
}
}
systemDao.deleteAllTagsOnServer();
}
}

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

@ -2,7 +2,7 @@ package ca.uhn.fhir.rest.server;
/*
* #%L
* HAPI FHIR - Core Library
* HAPI FHIR Structures - DSTU1 (FHIR v0.80)
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%

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">