diff --git a/hapi-fhir-base/.project b/hapi-fhir-base/.project index 790dcdc0123..7afe06363c3 100644 --- a/hapi-fhir-base/.project +++ b/hapi-fhir-base/.project @@ -15,6 +15,11 @@ + + org.eclipse.m2e.core.maven2Builder + + + org.eclipse.jem.workbench.JavaEMFNature diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index c1fe60cc1f4..0931f84ef90 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -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 { +public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding { protected static final Set ALLOWED_PARAMS; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReturningMethodBinding.class); @@ -102,8 +102,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding 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 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 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 parameters = theRequest.getParameters(); @@ -323,17 +347,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 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 includes = getRequestIncludesFromParams(params); @@ -344,32 +358,18 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 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= 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 responseResources = retVal.getResources(0, 1); IBaseResource responseResource = responseResources.get(0); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java index c26ede12486..b4aa63017d7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java @@ -37,6 +37,7 @@ import ca.uhn.fhir.rest.server.RestfulServer; public class RequestDetails { + private Map> myHeaders = new HashMap>(); 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 list = myHeaders.get(lowerCase); + if (list == null) { + list = new ArrayList(); + myHeaders.put(lowerCase, list); + } + list.add(theValue); + } + + public String getFirstHeader(String theName) { + ArrayList list = myHeaders.get(theName.toLowerCase()); + if (list == null || list.isEmpty()) { + return null; + } + return list.get(0); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index 3f4026bb9f1..73a936c60d1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -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); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index a18a8dd5544..ef00e1a79e6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -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 iter = theRequest.getHeaderNames(); iter.hasMoreElements(); ) { + String nextName = iter.nextElement(); + for (Enumeration 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 params = new HashMap(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. diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index f0cf84d7250..5f13cb2b324 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -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 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; + } + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java index b5dfc950b2a..8359edae06d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java @@ -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); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 570c416f71c..3a9292b9323 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -802,22 +802,7 @@ public abstract class BaseHapiFhirDao implements IDao { public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) { SearchParameterMap paramMap = new SearchParameterMap(); - List 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 parameters = translateMatchUrl(theMatchUrl); ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); for (NameValuePair next : parameters) { @@ -891,6 +876,26 @@ public abstract class BaseHapiFhirDao implements IDao { return paramMap; } + protected static List translateMatchUrl(String theMatchUrl) { + List 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"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index 7e24a4a1785..da7c2077323 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -22,11 +22,10 @@ package ca.uhn.fhir.jpa.dao; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.hamcrest.Matchers.emptyCollectionOf; +import java.util.Collection; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -34,6 +33,7 @@ import java.util.Set; import javax.persistence.TypedQuery; +import org.apache.http.NameValuePair; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; @@ -44,10 +44,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; +import com.google.common.collect.ArrayListMultimap; + import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt; @@ -61,16 +62,19 @@ import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.method.MethodUtil; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding; +import ca.uhn.fhir.rest.method.BaseResourceReturningMethodBinding.ResourceOrDstu1Bundle; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.EncodingEnum; -import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; +import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.UrlUtil; @@ -103,8 +107,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { resp.addEntry().setResource(ooResp); /* - * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it - * doesn't prevent others + * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it doesn't prevent others */ for (final Entry nextRequestEntry : theRequest.getEntry()) { @@ -129,8 +132,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { 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()); @@ -229,7 +231,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { if (res != null) { nextResourceId = res.getId(); - + if (nextResourceId.hasIdPart() == false) { if (isNotBlank(nextEntry.getFullUrl())) { nextResourceId = new IdDt(nextEntry.getFullUrl()); @@ -239,7 +241,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { if (nextResourceId.hasIdPart() && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*") && !isPlaceholder(nextResourceId)) { throw new InvalidRequestException("Invalid placeholder ID found: " + nextResourceId.getIdPart() + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'"); } - + if (nextResourceId.hasIdPart() && !nextResourceId.hasResourceType() && !isPlaceholder(nextResourceId)) { nextResourceId = new IdDt(toResourceName(res.getClass()), nextResourceId.getIdPart()); res.setId(nextResourceId); @@ -319,72 +321,67 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { } 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 paramValues = ArrayListMultimap.create(); + requestDetails.setParameters(new HashMap()); + if (qIndex != -1) { + String params = url.substring(qIndex); + List parameters = translateMatchUrl(params); + for (NameValuePair next : parameters) { + paramValues.put(next.getName(), next.getValue()); + } + for (java.util.Map.Entry> nextParamEntry : paramValues.asMap().entrySet()) { + String[] nextValue = nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]); + requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue); + } + url = url.substring(0, qIndex); } - if (parts.getResourceId() != null && parts.getParams() == null) { - /* - * GET is a read - */ - IResource found; - boolean notChanged = false; - if (parts.getVersionId() != null) { - if (isNotBlank(ifNoneMatch)) { - throw new InvalidRequestException("Unable to perform vread on '" + url + "' with ifNoneMatch also set. Do not include a version in the URL to perform a conditional read."); - } - found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId())); - } else { - found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId())); - if (isNotBlank(ifNoneMatch) && ifNoneMatch.equals(found.getId().getVersionIdPart())) { - notChanged = true; + requestDetails.setRequestPath(url); + requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase()); + + theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url); + BaseMethodBinding method = theRequestDetails.getServer().determineResourceMethod(requestDetails, url); + if (method == null) { + throw new IllegalArgumentException("Unable to handle GET " + url); + } + + if (isNotBlank(nextEntry.getRequest().getIfMatch())) { + requestDetails.addHeader(Constants.HEADER_IF_MATCH, nextEntry.getRequest().getIfMatch()); + } + if (isNotBlank(nextEntry.getRequest().getIfNoneExist())) { + requestDetails.addHeader(Constants.HEADER_IF_NONE_EXIST, nextEntry.getRequest().getIfNoneExist()); + } + if (isNotBlank(nextEntry.getRequest().getIfNoneMatch())) { + requestDetails.addHeader(Constants.HEADER_IF_NONE_MATCH, nextEntry.getRequest().getIfNoneMatch()); + } + + if (method instanceof BaseResourceReturningMethodBinding) { + try { + ResourceOrDstu1Bundle responseData = ((BaseResourceReturningMethodBinding) method).invokeServer(theRequestDetails.getServer(), requestDetails, new byte[0]); + Entry newEntry = response.addEntry(); + IBaseResource resource = responseData.getResource(); + if (paramValues.containsKey(Constants.PARAM_SUMMARY) || paramValues.containsKey(Constants.PARAM_CONTENT)) { + resource = filterNestedBundle(requestDetails, resource); } + newEntry.setResource((IResource) resource); + newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK)); + } catch (NotModifiedException e) { + Entry newEntry = response.addEntry(); + newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED)); } - Entry entry = response.addEntry(); - if (notChanged == false) { - entry.setResource(found); - } - EntryResponse resp = entry.getResponse(); - resp.setLocation(found.getId().toUnqualified().getValue()); - resp.setEtag(found.getId().getVersionIdPart()); - if (!notChanged) { - resp.setStatus(toStatusString(Constants.STATUS_HTTP_200_OK)); - } else { - resp.setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED)); - } - } else if (parts.getParams() != null) { - /* - * GET is a search - */ - RuntimeResourceDefinition def = getContext().getResourceDefinition(dao.getResourceType()); - SearchParameterMap params = translateMatchUrl(url, def); - IBundleProvider bundle = dao.search(params); - - Integer count = params.getCount(); - if (count == null) { - count = bundle.preferredPageSize(); - } - - IVersionSpecificBundleFactory bundleFactory = getContext().newBundleFactory(); - - Set includes = new HashSet(); - EncodingEnum linkEncoding = null; - bundleFactory.initializeBundleFromBundleProvider(theRequestDetails.getServer(), bundle, linkEncoding, theRequestDetails.getFhirServerBase(), url, false, 0, count, null, ca.uhn.fhir.model.valueset.BundleTypeEnum.SEARCHSET, includes); - - Entry newEntry = response.addEntry(); - newEntry.setResource((IResource) bundleFactory.getResourceBundle()); - newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK)); + } else { + throw new IllegalArgumentException("Unable to handle GET " + url); } } } - } FhirTerser terser = getContext().newTerser(); @@ -428,7 +425,8 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { IFhirResourceDao resourceDao = getDao(nextEntry.getResource().getClass()); Set val = resourceDao.processMatchUrl(matchUrl); if (val.size() > 1) { - throw new InvalidRequestException("Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); + throw new InvalidRequestException( + "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?"); } } } @@ -454,6 +452,20 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { 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 toDao(UrlParts theParts, String theVerb, String theUrl) { RuntimeResourceDefinition resType; try { @@ -471,10 +483,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { throw new InvalidRequestException(msg); } - if (theParts.getResourceId() == null && theParts.getParams() == null) { - String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); - throw new InvalidRequestException(msg); - } + // if (theParts.getResourceId() == null && theParts.getParams() == null) { + // String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl); + // throw new InvalidRequestException(msg); + // } return dao; } @@ -487,15 +499,15 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { return retVal; } - private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry, String theResourceType, IResource theRes) { + private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map 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); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java index 5f87159dfc9..22efc907338 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaProvider.java @@ -60,7 +60,7 @@ public class BaseJpaProvider { Enumeration forwardedFors = theRequest.getHeaders("x-forwarded-for"); StringBuilder b = new StringBuilder(); - for (Enumeration enums = forwardedFors; enums.hasMoreElements();) { + for (Enumeration enums = forwardedFors; enums != null && enums.hasMoreElements();) { if (b.length() > 0) { b.append(" / "); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java index c68c0246e0b..e8692018115 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java @@ -22,8 +22,14 @@ import static org.mockito.Mockito.when; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; import java.util.List; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.Before; @@ -37,6 +43,7 @@ import ca.uhn.fhir.jpa.entity.ResourceEncodingEnum; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test; +import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TagList; @@ -63,6 +70,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; @@ -74,6 +82,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2Test.class); private RequestDetails myRequestDetails; + private RestfulServer myServer; @Test public void testTransactionFromBundle6() throws Exception { @@ -310,10 +319,25 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test { } + @SuppressWarnings("unchecked") @Before - public void before() { + public void before() throws ServletException { myRequestDetails = mock(RequestDetails.class); - when(myRequestDetails.getServer()).thenReturn(new RestfulServer()); + + if (myServer == null) { + myServer = new RestfulServer(myFhirCtx); + + PatientResourceProvider patientRp = new PatientResourceProvider(); + patientRp.setDao(myPatientDao); + myServer.setResourceProviders(patientRp); + myServer.init(mock(ServletConfig.class)); + } + + when(myRequestDetails.getServer()).thenReturn(myServer); + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + when(myRequestDetails.getServletRequest()).thenReturn(servletRequest); + when(servletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class)); + when(servletRequest.getRequestURL()).thenReturn(new StringBuffer("/Patient")); } @Test @@ -1029,8 +1053,8 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test { assertEquals("200 OK", nextEntry.getResponse().getStatus()); nextEntry = resp.getEntry().get(2); - assertNull(nextEntry.getResource()); assertEquals("304 Not Modified", nextEntry.getResponse().getStatus()); + assertNull(nextEntry.getResource()); } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java index cd6dba0bd76..0b5c23ba4b8 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SystemProviderDstu2Test.java @@ -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,18 +44,95 @@ 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; + + @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 { + myRestServer.setDefaultResponseEncoding(EncodingEnum.JSON); + myRestServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10)); + ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor(); + myRestServer.registerInterceptor(interceptor); + + for (int i = 0; i < 11; i++) { + Patient p = new Patient(); + p.addName().addFamily("Name" + i); + ourClient.create().resource(p).execute(); + } + + HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); + get.addHeader("Accept", "application/xml, text/html"); + CloseableHttpResponse http = ourHttpClient.execute(get); + try { + String response = IOUtils.toString(http.getEntity().getContent()); + ourLog.info(response); + assertThat(response, not(containsString("_format"))); + assertEquals(200, http.getStatusLine().getStatusCode()); + } finally { + http.close(); + } + + myRestServer.unregisterInterceptor(interceptor); + } @Test public void testEverythingType() throws Exception { @@ -71,74 +146,9 @@ public class SystemProviderDstu2Test extends BaseJpaTest { } @Test - public void testEverythingReturnsCorrectFormatInPagingLink() throws Exception { - restServer.setDefaultResponseEncoding(EncodingEnum.JSON); - restServer.setPagingProvider(new FifoMemoryPagingProvider(1).setDefaultPageSize(10)); - ResponseHighlighterInterceptor interceptor = new ResponseHighlighterInterceptor(); - restServer.registerInterceptor(interceptor); - - for (int i = 0; i < 11; i++) { - Patient p = new Patient(); - p.addName().addFamily("Name" + i); - ourClient.create().resource(p).execute(); - } - - HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything"); - get.addHeader("Accept", "application/xml, text/html"); - CloseableHttpResponse http = ourHttpClient.execute(get); - try { - String response = IOUtils.toString(http.getEntity().getContent()); - ourLog.info(response); - assertThat(response, not(containsString("_format"))); - assertEquals(200, http.getStatusLine().getStatusCode()); - } finally { - http.close(); - } - - restServer.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); - 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()); -// } + 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 patientDao = (IFhirResourceDao) ourAppCtx.getBean("myPatientDaoDstu2", IFhirResourceDao.class); - PatientResourceProvider patientRp = new PatientResourceProvider(); - patientRp.setDao(patientDao); - - IFhirResourceDao questionnaireDao = (IFhirResourceDao) ourAppCtx.getBean("myQuestionnaireDaoDstu2", IFhirResourceDao.class); - QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2(); - questionnaireRp.setDao(questionnaireDao); - - IFhirResourceDao observationDao = (IFhirResourceDao) ourAppCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class); - ObservationResourceProvider observationRp = new ObservationResourceProvider(); - observationRp.setDao(observationDao); - - IFhirResourceDao organizationDao = (IFhirResourceDao) 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); } } diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml_ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml_ deleted file mode 100644 index 5acd1694112..00000000000 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml_ +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - ca.uhn.fhir.jpa.entity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml_ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml_ deleted file mode 100644 index 9c14d15cbc9..00000000000 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml_ +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - ca.uhn.fhir.jpa.entity - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html index 7efd7b63486..4bdc287ed34 100644 --- a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html +++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/tmpl-navbar-top.html @@ -11,7 +11,7 @@ - HAPI FHIR + Home