From ab17c9f3d17bc8464b86d0b261d00cda5341f6df Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Thu, 21 Aug 2014 00:34:19 -0400 Subject: [PATCH] Let server return profile info if configured to do so --- .../ca/uhn/fhir/context/ModelScanner.java | 2 - .../context/RuntimeResourceDefinition.java | 4 + .../ca/uhn/fhir/rest/client/BaseClient.java | 6 +- .../fhir/rest/server/AddProfileTagEnum.java | 44 + .../uhn/fhir/rest/server/RestfulServer.java | 761 ++++++++++-------- .../ca/uhn/fhir/parser/JsonParserTest.java | 19 +- .../rest/client/ExceptionHandlingTest.java | 43 +- .../uhn/fhir/rest/server/CustomTypeTest.java | 85 +- .../resources/example-patient-general.json | 301 +------ .../org.eclipse.core.resources.prefs | 2 + .../main/java/ca/uhn/fhir/to/Controller.java | 7 +- .../org.eclipse.core.resources.prefs | 1 + 12 files changed, 591 insertions(+), 684 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/AddProfileTagEnum.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index ae4450aab70..0708b81cd13 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -629,8 +629,6 @@ class ModelScanner { } } - String profile = resourceDefinition.profile(); - RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(resourceName, theClass, resourceDefinition); myClassToElementDefinitions.put(theClass, resourceDef); if (primaryNameProvider) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java index 74195f4bf05..74ff3894696 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java @@ -352,4 +352,8 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini return retVal; } + public boolean isStandardProfile() { + return myResourceProfile.startsWith("http://hl7.org/fhir/profiles"); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index 3687b01ced6..f176b48317a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -211,6 +211,7 @@ public abstract class BaseClient { } String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); + OperationOutcome oo=null; if (Constants.CT_TEXT.equals(mimeType)) { message = message + ": " + body; } else { @@ -218,7 +219,7 @@ public abstract class BaseClient { if (enc != null) { IParser p = enc.newParser(theContext); try { - OperationOutcome oo = p.parseResource(OperationOutcome.class, body); + oo = p.parseResource(OperationOutcome.class, body); if (oo.getIssueFirstRep().getDetails().isEmpty()==false) { message = message + ": " + oo.getIssueFirstRep().getDetails().getValue(); } @@ -231,7 +232,8 @@ public abstract class BaseClient { keepResponseAndLogIt(theLogRequestAndResponse, response, body); BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), message); - + exception.setOperationOutcome(oo); + if (body != null) { exception.setResponseBody(body); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/AddProfileTagEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/AddProfileTagEnum.java new file mode 100644 index 00000000000..19f28b4b039 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/AddProfileTagEnum.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.rest.server; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 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% + */ + +/** + * RESTful server behaviour for automatically adding profile tags + * + * @see RestfulServer#setAddProfileTag(AddProfileTagEnum) + */ +enum AddProfileTagEnum { + /** + * Do not add profile tags automatically + */ + NEVER, + + /** + * Add any profile tags that returned resources appear to conform to + */ + ALWAYS, + + /** + * Add any profile tags that returned resources appear to conform to if the resource is a non-standard class (e.g. + * it is an instance of a class that extends a built in type, but adds or constrains it) + */ + ONLY_FOR_CUSTOM +} \ No newline at end of file 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 ac48f1eaca4..84bcfb40350 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 @@ -55,7 +55,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; @@ -87,6 +86,7 @@ public class RestfulServer extends HttpServlet { private static final long serialVersionUID = 1L; + private AddProfileTagEnum myAddProfileTag; private FhirContext myFhirContext; private String myImplementationDescription; private ResourceBinding myNullResourceBinding = new ResourceBinding(); @@ -119,42 +119,316 @@ public class RestfulServer extends HttpServlet { /** * This method is called prior to sending a response to incoming requests. It is used to add custom headers. *

- * Use caution if overriding this method: it is recommended to call super.addHeadersToResponse to avoid inadvertantly disabling functionality. + * Use caution if overriding this method: it is recommended to call super.addHeadersToResponse to avoid + * inadvertantly disabling functionality. *

*/ public void addHeadersToResponse(HttpServletResponse theHttpResponse) { theHttpResponse.addHeader("X-Powered-By", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server"); } + /** + * Returns the setting for automatically adding profile tags + * + * @see #setAddProfileTag(AddProfileTagEnum) + */ + public AddProfileTagEnum getAddProfileTag() { + return myAddProfileTag; + } + + /** + * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain + * providers should generally use this context if one is needed, as opposed to creating their own. + */ + public FhirContext getFhirContext() { + return myFhirContext; + } + + public String getImplementationDescription() { + return myImplementationDescription; + } + + public IPagingProvider getPagingProvider() { + return myPagingProvider; + } + + /** + * Provides the non-resource specific providers which implement method calls on this server + * + * @see #getResourceProviders() + */ + public Collection getPlainProviders() { + return myPlainProviders; + } + + public Collection getResourceBindings() { + return myResourceNameToProvider.values(); + } + + /** + * Provides the resource providers for this server + */ + public Collection getResourceProviders() { + return myResourceProviders; + } + + /** + * Provides the security manager, or null if none + */ + public ISecurityManager getSecurityManager() { + return mySecurityManager; + } + + /** + * Get the server address strategy, which is used to determine what base URL to provide clients to refer to this + * server. Defaults to an instance of {@link IncomingRequestAddressStrategy} + */ + public IServerAddressStrategy getServerAddressStrategy() { + return myServerAddressStrategy; + } + + /** + * Returns the server conformance provider, which is the provider that is used to generate the server's conformance + * (metadata) statement. + *

+ * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to null + * if you do not wish to export a conformance statement. + *

+ */ + public Object getServerConformanceProvider() { + return myServerConformanceProvider; + } + + /** + * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, + * but can be helpful to set with something appropriate. + * + * @see RestfulServer#setServerName(String) + */ + public String getServerName() { + return myServerName; + } + + public IResourceProvider getServerProfilesProvider() { + return new ServerProfileProvider(getFhirContext()); + } + + /** + * Gets the server's version, as exported in conformance profiles exported by the server. This is informational + * only, but can be helpful to set with something appropriate. + */ + public String getServerVersion() { + return myServerVersion; + } + + /** + * 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. + */ + @Override + public final void init() throws ServletException { + initialize(); + try { + ourLog.info("Initializing HAPI FHIR restful server"); + + mySecurityManager = getSecurityManager(); + if (null == mySecurityManager) { + ourLog.trace("No security manager has been provided"); + } + + Collection resourceProvider = getResourceProviders(); + if (resourceProvider != null) { + Map, IResourceProvider> typeToProvider = new HashMap, IResourceProvider>(); + for (IResourceProvider nextProvider : resourceProvider) { + Class resourceType = nextProvider.getResourceType(); + if (resourceType == null) { + throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null"); + } + if (typeToProvider.containsKey(resourceType)) { + throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName()); + } + typeToProvider.put(resourceType, nextProvider); + } + ourLog.info("Got {} resource providers", typeToProvider.size()); + for (IResourceProvider provider : typeToProvider.values()) { + assertProviderIsValid(provider); + findResourceMethods(provider); + } + } + + Collection providers = getPlainProviders(); + if (providers != null) { + for (Object next : providers) { + assertProviderIsValid(next); + findResourceMethods(next); + } + } + + findResourceMethods(getServerProfilesProvider()); + findSystemMethods(getServerConformanceProvider()); + + } catch (Exception ex) { + ourLog.error("An error occurred while loading request handlers!", ex); + throw new ServletException("Failed to initialize FHIR Restful server", ex); + } + + myStarted = true; + ourLog.info("A FHIR has been lit on this server"); + } + + public boolean isUseBrowserFriendlyContentTypes() { + return myUseBrowserFriendlyContentTypes; + } + + /** + * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} + * (which is the default), the server will automatically add a profile tag based on the class of the resource(s) + * being returned. + * + * @param theAddProfileTag + * The behaviour enum (must not be null) + */ + public void setAddProfileTag(AddProfileTagEnum theAddProfileTag) { + Validate.notNull(theAddProfileTag, "theAddProfileTag must not be null"); + myAddProfileTag = theAddProfileTag; + } + + public void setFhirContext(FhirContext theFhirContext) { + Validate.notNull(theFhirContext, "FhirContext must not be null"); + myFhirContext = theFhirContext; + } + + public void setImplementationDescription(String theImplementationDescription) { + myImplementationDescription = theImplementationDescription; + } + + /** + * Sets the paging provider to use, or null to use no paging (which is the default) + */ + public void setPagingProvider(IPagingProvider thePagingProvider) { + myPagingProvider = thePagingProvider; + } + + /** + * Sets the non-resource specific providers which implement method calls on this server. + * + * @see #setResourceProviders(Collection) + */ + public void setPlainProviders(Collection theProviders) { + myPlainProviders = theProviders; + } + + /** + * Sets the non-resource specific providers which implement method calls on this server. + * + * @see #setResourceProviders(Collection) + */ + public void setPlainProviders(Object... theProv) { + setPlainProviders(Arrays.asList(theProv)); + } + + /** + * Sets the non-resource specific providers which implement method calls on this server + * + * @see #setResourceProviders(Collection) + */ + public void setProviders(Object... theProviders) { + myPlainProviders = Arrays.asList(theProviders); + } + + /** + * Sets the resource providers for this server + */ + public void setResourceProviders(Collection theResourceProviders) { + myResourceProviders = theResourceProviders; + } + + /** + * Sets the resource providers for this server + */ + public void setResourceProviders(IResourceProvider... theResourceProviders) { + myResourceProviders = Arrays.asList(theResourceProviders); + } + + /** + * Sets the security manager, or null if none + */ + public void setSecurityManager(ISecurityManager theSecurityManager) { + mySecurityManager = theSecurityManager; + } + + /** + * Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this + * server. Defaults to an instance of {@link IncomingRequestAddressStrategy} + */ + public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) { + Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null"); + myServerAddressStrategy = theServerAddressStrategy; + } + + /** + * Returns the server conformance provider, which is the provider that is used to generate the server's conformance + * (metadata) statement. + *

+ * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to null + * if you do not wish to export a conformance statement. + *

+ * Note that this method can only be called before the server is initialized. + * + * @throws IllegalStateException + * Note that this method can only be called prior to {@link #init() initialization} and will throw an + * {@link IllegalStateException} if called after that. + */ + public void setServerConformanceProvider(Object theServerConformanceProvider) { + if (myStarted) { + throw new IllegalStateException("Server is already started"); + } + myServerConformanceProvider = theServerConformanceProvider; + } + + /** + * Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, + * but can be helpful to set with something appropriate. + */ + public void setServerName(String theServerName) { + myServerName = theServerName; + } + + /** + * Gets the server's version, as exported in conformance profiles exported by the server. This is informational + * only, but can be helpful to set with something appropriate. + */ + public void setServerVersion(String theServerVersion) { + myServerVersion = theServerVersion; + } + + /** + * If set to true (default is false), the server will use browser friendly content-types (instead of + * standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR + */ + public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) { + myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes; + } + private void assertProviderIsValid(Object theNext) throws ConfigurationException { if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) { throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class ust be public"); } } - @Override - protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethodBinding.RequestType.DELETE, request, response); - } - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethodBinding.RequestType.GET, request, response); - } - - @Override - protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException { - handleRequest(SearchMethodBinding.RequestType.OPTIONS, theReq, theResp); - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethodBinding.RequestType.POST, request, response); - } - - @Override - protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethodBinding.RequestType.PUT, request, response); + /** + * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20) + */ + private int escapedLength(String theServletPath) { + int delta = 0; + for (int i = 0; i < theServletPath.length(); i++) { + char next = theServletPath.charAt(i); + if (next == ' ') { + delta = delta + 2; + } + } + return theServletPath.length() + delta; } private void findResourceMethods(Object theProvider) throws Exception { @@ -176,6 +450,23 @@ public class RestfulServer extends HttpServlet { } } + // /** + // * Sets the {@link INarrativeGenerator Narrative Generator} to use when + // serializing responses from this server, or null (which is + // the default) to disable narrative generation. + // * Note that this method can only be called before the server is + // initialized. + // * + // * @throws IllegalStateException + // * Note that this method can only be called prior to {@link #init() + // initialization} and will throw an {@link IllegalStateException} if called + // after that. + // */ + // public void setNarrativeGenerator(INarrativeGenerator + // theNarrativeGenerator) { + // myNarrativeGenerator = theNarrativeGenerator; + // } + private int findResourceMethods(Object theProvider, Class clazz) throws ConfigurationException { int count = 0; @@ -249,86 +540,6 @@ public class RestfulServer extends HttpServlet { } } - /** - * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to - * creating their own. - */ - public FhirContext getFhirContext() { - return myFhirContext; - } - - public String getImplementationDescription() { - return myImplementationDescription; - } - - public IPagingProvider getPagingProvider() { - return myPagingProvider; - } - - /** - * Provides the non-resource specific providers which implement method calls on this server - * - * @see #getResourceProviders() - */ - public Collection getPlainProviders() { - return myPlainProviders; - } - - public Collection getResourceBindings() { - return myResourceNameToProvider.values(); - } - - /** - * Provides the resource providers for this server - */ - public Collection getResourceProviders() { - return myResourceProviders; - } - - /** - * Provides the security manager, or null if none - */ - public ISecurityManager getSecurityManager() { - return mySecurityManager; - } - - /** - * Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy} - */ - public IServerAddressStrategy getServerAddressStrategy() { - return myServerAddressStrategy; - } - - /** - * Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement. - *

- * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to null if you do not wish to export a conformance statement. - *

- */ - public Object getServerConformanceProvider() { - return myServerConformanceProvider; - } - - /** - * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate. - * - * @see RestfulServer#setServerName(String) - */ - public String getServerName() { - return myServerName; - } - - public IResourceProvider getServerProfilesProvider() { - return new ServerProfileProvider(getFhirContext()); - } - - /** - * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate. - */ - public String getServerVersion() { - return myServerVersion; - } - private void handlePagingRequest(Request theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException { IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction); if (resultList == null) { @@ -361,11 +572,40 @@ public class RestfulServer extends HttpServlet { boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest()); NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest); boolean respondGzip = theRequest.isRespondGzip(); - streamResponseAsBundle(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, - count, thePagingAction, respondGzip); + streamResponseAsBundle(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction, respondGzip); } + private boolean requestIsBrowser(HttpServletRequest theRequest) { + String userAgent = theRequest.getHeader("User-Agent"); + return userAgent != null && userAgent.contains("Mozilla"); + } + + @Override + protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + handleRequest(SearchMethodBinding.RequestType.DELETE, request, response); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + handleRequest(SearchMethodBinding.RequestType.GET, request, response); + } + + @Override + protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException { + handleRequest(SearchMethodBinding.RequestType.OPTIONS, theReq, theResp); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + handleRequest(SearchMethodBinding.RequestType.POST, request, response); + } + + @Override + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + handleRequest(SearchMethodBinding.RequestType.PUT, request, response); + } + protected void handleRequest(SearchMethodBinding.RequestType theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException { String fhirServerBase = null; try { @@ -547,14 +787,14 @@ public class RestfulServer extends HttpServlet { } catch (Throwable e) { - OperationOutcome oo=null; + OperationOutcome oo = null; int statusCode = 500; - + if (e instanceof BaseServerResponseException) { oo = ((BaseServerResponseException) e).getOperationOutcome(); statusCode = ((BaseServerResponseException) e).getStatusCode(); } - + if (oo == null) { oo = new OperationOutcome(); Issue issue = oo.addIssue(); @@ -586,218 +826,13 @@ public class RestfulServer extends HttpServlet { } /** - * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20) - */ - private int escapedLength(String theServletPath) { - int delta = 0; - for (int i = 0; i < theServletPath.length(); i++) { - char next = theServletPath.charAt(i); - if (next == ' ') { - delta = delta + 2; - } - } - return theServletPath.length() + delta; - } - - /** - * 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. - */ - @Override - public final void init() throws ServletException { - initialize(); - try { - ourLog.info("Initializing HAPI FHIR restful server"); - - mySecurityManager = getSecurityManager(); - if (null == mySecurityManager) { - ourLog.trace("No security manager has been provided"); - } - - Collection resourceProvider = getResourceProviders(); - if (resourceProvider != null) { - Map, IResourceProvider> typeToProvider = new HashMap, IResourceProvider>(); - for (IResourceProvider nextProvider : resourceProvider) { - Class resourceType = nextProvider.getResourceType(); - if (resourceType == null) { - throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null"); - } - if (typeToProvider.containsKey(resourceType)) { - throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName()); - } - typeToProvider.put(resourceType, nextProvider); - } - ourLog.info("Got {} resource providers", typeToProvider.size()); - for (IResourceProvider provider : typeToProvider.values()) { - assertProviderIsValid(provider); - findResourceMethods(provider); - } - } - - Collection providers = getPlainProviders(); - if (providers != null) { - for (Object next : providers) { - assertProviderIsValid(next); - findResourceMethods(next); - } - } - - findResourceMethods(getServerProfilesProvider()); - findSystemMethods(getServerConformanceProvider()); - - } catch (Exception ex) { - ourLog.error("An error occurred while loading request handlers!", ex); - throw new ServletException("Failed to initialize FHIR Restful server", ex); - } - - myStarted = true; - ourLog.info("A FHIR has been lit on this server"); - } - - /** - * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used. + * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the + * server being used. */ protected void initialize() throws ServletException { // nothing by default } - public boolean isUseBrowserFriendlyContentTypes() { - return myUseBrowserFriendlyContentTypes; - } - - private boolean requestIsBrowser(HttpServletRequest theRequest) { - String userAgent = theRequest.getHeader("User-Agent"); - return userAgent != null && userAgent.contains("Mozilla"); - } - - public void setFhirContext(FhirContext theFhirContext) { - Validate.notNull(theFhirContext, "FhirContext must not be null"); - myFhirContext = theFhirContext; - } - - public void setImplementationDescription(String theImplementationDescription) { - myImplementationDescription = theImplementationDescription; - } - - /** - * Sets the paging provider to use, or null to use no paging (which is the default) - */ - public void setPagingProvider(IPagingProvider thePagingProvider) { - myPagingProvider = thePagingProvider; - } - - // /** - // * Sets the {@link INarrativeGenerator Narrative Generator} to use when - // serializing responses from this server, or null (which is - // the default) to disable narrative generation. - // * Note that this method can only be called before the server is - // initialized. - // * - // * @throws IllegalStateException - // * Note that this method can only be called prior to {@link #init() - // initialization} and will throw an {@link IllegalStateException} if called - // after that. - // */ - // public void setNarrativeGenerator(INarrativeGenerator - // theNarrativeGenerator) { - // myNarrativeGenerator = theNarrativeGenerator; - // } - - /** - * Sets the non-resource specific providers which implement method calls on this server. - * - * @see #setResourceProviders(Collection) - */ - public void setPlainProviders(Collection theProviders) { - myPlainProviders = theProviders; - } - - /** - * Sets the non-resource specific providers which implement method calls on this server. - * - * @see #setResourceProviders(Collection) - */ - public void setPlainProviders(Object... theProv) { - setPlainProviders(Arrays.asList(theProv)); - } - - /** - * Sets the non-resource specific providers which implement method calls on this server - * - * @see #setResourceProviders(Collection) - */ - public void setProviders(Object... theProviders) { - myPlainProviders = Arrays.asList(theProviders); - } - - /** - * Sets the resource providers for this server - */ - public void setResourceProviders(Collection theResourceProviders) { - myResourceProviders = theResourceProviders; - } - - /** - * Sets the resource providers for this server - */ - public void setResourceProviders(IResourceProvider... theResourceProviders) { - myResourceProviders = Arrays.asList(theResourceProviders); - } - - /** - * Sets the security manager, or null if none - */ - public void setSecurityManager(ISecurityManager theSecurityManager) { - mySecurityManager = theSecurityManager; - } - - /** - * Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy} - */ - public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) { - Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null"); - myServerAddressStrategy = theServerAddressStrategy; - } - - /** - * Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement. - *

- * By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to null if you do not wish to export a conformance statement. - *

- * Note that this method can only be called before the server is initialized. - * - * @throws IllegalStateException - * Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that. - */ - public void setServerConformanceProvider(Object theServerConformanceProvider) { - if (myStarted) { - throw new IllegalStateException("Server is already started"); - } - myServerConformanceProvider = theServerConformanceProvider; - } - - /** - * Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate. - */ - public void setServerName(String theServerName) { - myServerName = theServerName; - } - - /** - * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate. - */ - public void setServerVersion(String theServerVersion) { - myServerVersion = theServerVersion; - } - - /** - * If set to true (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser - * instead of a FHIR - */ - public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) { - myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes; - } - public static Bundle createBundleFromResourceList(FhirContext theContext, String theAuthor, List theResult, String theServerBase, String theCompleteUrl, int theTotalResults) { Bundle bundle = new Bundle(); bundle.getAuthorName().setValue(theAuthor); @@ -844,21 +879,20 @@ public class RestfulServer extends HttpServlet { } } } - + // Linked resources may themselves have linked resources references = new ArrayList(); for (IResource iResource : addedResourcesThisPass) { List newReferences = theContext.newTerser().getAllPopulatedChildElementsOfType(iResource, ResourceReferenceDt.class); references.addAll(newReferences); } - + addedResources.addAll(addedResourcesThisPass); - + } while (references.isEmpty() == false); - + bundle.addResource(next, theContext, theServerBase); -// addProfileToBundleEntry(theContext, next, entry); - + } /* @@ -866,26 +900,12 @@ public class RestfulServer extends HttpServlet { */ for (IResource next : addedResources) { bundle.addResource(next, theContext, theServerBase); -// addProfileToBundleEntry(theContext, next, entry); } bundle.getTotalResults().setValue(theTotalResults); return bundle; } - /* - private static void addProfileToBundleEntry(FhirContext theContext, IResource next, BundleEntry entry) { - List profileTags = entry.getCategories().getTagsWithScheme(Tag.HL7_ORG_PROFILE_TAG); - if (profileTags.isEmpty()) { - RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(next); - String profile = nextDef.getResourceProfile(); - if (isNotBlank(profile)) { - entry.addCategory(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null)); - } - } - } - */ - public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) { StringBuilder b = new StringBuilder(); b.append(theServerBase); @@ -1012,17 +1032,6 @@ public class RestfulServer extends HttpServlet { return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS); } - private static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException { - Writer writer; - if (theRespondGzip) { - theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); - writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8"); - } else { - writer = theHttpResponse.getWriter(); - } - return writer; - } - public static boolean prettyPrintResponse(Request theRequest) { Map requestParams = theRequest.getParameters(); String[] pretty = requestParams.remove(Constants.PARAM_PRETTY); @@ -1048,9 +1057,8 @@ public class RestfulServer extends HttpServlet { return prettyPrint; } - public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, - String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId, boolean theRespondGzip) - throws IOException { + public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, + NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId, boolean theRespondGzip) throws IOException { assert !theServerBase.endsWith("/"); theHttpResponse.setStatus(200); @@ -1102,6 +1110,16 @@ public class RestfulServer extends HttpServlet { } } + if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) { + for (int i = 0; i < resourceList.size(); i++) { + IResource nextRes = resourceList.get(i); + RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(nextRes); + if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) { + addProfileToBundleEntry(theServer.getFhirContext(), nextRes); + } + } + } + Bundle bundle = createBundleFromResourceList(theServer.getFhirContext(), theServer.getServerName(), resourceList, theServerBase, theCompleteUrl, theResult.size()); bundle.setPublished(theResult.getPublished()); @@ -1137,14 +1155,40 @@ public class RestfulServer extends HttpServlet { } } - public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, - boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase) throws IOException { + public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, + String theServerBase) throws IOException { int stausCode = 200; streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase); } - private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, - boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip, String theServerBase) throws IOException { + private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource) { + + TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource); + if (tl == null) { + tl = new TagList(); + ResourceMetadataKeyEnum.TAG_LIST.put(theResource, tl); + } + + RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource); + String profile = nextDef.getResourceProfile(); + if (isNotBlank(profile)) { + tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null)); + } + } + + private static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException { + Writer writer; + if (theRespondGzip) { + theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); + writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8"); + } else { + writer = theHttpResponse.getWriter(); + } + return writer; + } + + private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, + boolean theRespondGzip, String theServerBase) throws IOException { theHttpResponse.setStatus(stausCode); if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) { @@ -1153,6 +1197,13 @@ public class RestfulServer extends HttpServlet { theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId); } + if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) { + RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource); + if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) { + addProfileToBundleEntry(theServer.getFhirContext(), theResource); + } + } + if (theResource instanceof Binary) { Binary bin = (Binary) theResource; if (isNotBlank(bin.getContentType())) { diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java index eccbd7e44e0..4c67e739156 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/parser/JsonParserTest.java @@ -28,6 +28,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.annotation.Child; @@ -432,7 +433,7 @@ public class JsonParserTest { ourLog.info(str); assertThat(str, StringContains.containsString("
AAA
")); - String substring = "\"resource\":\"#"; + String substring = "\"reference\":\"#"; assertThat(str, StringContains.containsString(substring)); int idx = str.indexOf(substring) + substring.length(); @@ -473,7 +474,7 @@ public class JsonParserTest { String val = parser.encodeResourceToString(patient); ourLog.info(val); - assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]")); + assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"reference\":\"Organization/123\"}}]")); MyPatientWithOneDeclaredExtension actual = parser.parseResource(MyPatientWithOneDeclaredExtension.class, val); assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); @@ -526,7 +527,7 @@ public class JsonParserTest { String val = parser.encodeResourceToString(patient); ourLog.info(val); - assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]")); + assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"reference\":\"Organization/123\"}}]")); Patient actual = parser.parseResource(Patient.class, val); assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); @@ -907,6 +908,18 @@ public class JsonParserTest { } + /** + * HAPI FHIR < 0.6 incorrectly used "resource" instead of "reference" + */ + @Test + public void testParseWithIncorrectReference() throws IOException { + String jsonString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.json")); + jsonString = jsonString.replace("\"reference\"", "\"resource\""); + Patient parsed = ourCtx.newJsonParser().parseResource(Patient.class,jsonString); + assertEquals("Organization/1", parsed.getManagingOrganization().getReference().getValue()); + } + + @Test public void testSimpleResourceEncodeWithCustomType() throws IOException { diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ExceptionHandlingTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ExceptionHandlingTest.java index 1b1592161c9..d7fc2606951 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ExceptionHandlingTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ExceptionHandlingTest.java @@ -1,10 +1,7 @@ package ca.uhn.fhir.rest.client; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.io.StringReader; import java.nio.charset.Charset; @@ -27,6 +24,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -138,17 +138,48 @@ public class ExceptionHandlingTest { when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - try { client.read(Patient.class, new IdDt("Patient/1234")); fail(); } catch (InternalErrorException e) { assertThat(e.getMessage(), StringContains.containsString("HTTP 500 Internal Error")); assertThat(e.getMessage(), StringContains.containsString("Help I'm a bug")); + assertNotNull(e.getOperationOutcome()); + assertEquals("Help I'm a bug",e.getOperationOutcome().getIssueFirstRep().getDetails().getValue()); } } + @Test + public void testFail500WithOperationOutcomeMessageGeneric() throws Exception { + OperationOutcome oo = new OperationOutcome(); + oo.getIssueFirstRep().getDetails().setValue("Help I'm a bug"); + String msg = myCtx.newJsonParser().encodeResourceToString(oo); + String contentType = Constants.CT_FHIR_JSON; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "Internal Error")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", contentType + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IMyClient client = myCtx.newRestfulClient(IMyClient.class,"http://example.com/fhir"); + try { + client.read(new IdDt("Patient/1234")); + fail(); + } catch (InternalErrorException e) { + assertThat(e.getMessage(), StringContains.containsString("HTTP 500 Internal Error")); + assertThat(e.getMessage(), StringContains.containsString("Help I'm a bug")); + assertNotNull(e.getOperationOutcome()); + assertEquals("Help I'm a bug",e.getOperationOutcome().getIssueFirstRep().getDetails().getValue()); + } + + } + public interface IMyClient extends IRestfulClient + { + @Read + Patient read(@IdParam IdDt theId); + } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CustomTypeTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CustomTypeTest.java index e37a11e15d5..c7137f350d7 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CustomTypeTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/CustomTypeTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -8,14 +8,10 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.message.BasicNameValuePair; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; @@ -29,16 +25,11 @@ import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.model.dstu.composite.CodingDt; -import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.testutil.RandomServerPortProvider; /** @@ -50,10 +41,14 @@ public class CustomTypeTest { private static FhirContext ourCtx = new FhirContext(ExtendedPatient.class); private static int ourPort; private static Server ourServer; + private static RestfulServer ourServlet; @Test public void testSearchReturnsProfile() throws Exception { + ourServlet.setAddProfileTag(AddProfileTagEnum.ONLY_FOR_CUSTOM); + ourReturnExtended=true; + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); HttpResponse status = ourClient.execute(httpGet); String responseContent = IOUtils.toString(status.getEntity().getContent()); @@ -72,7 +67,65 @@ public class CustomTypeTest { } + @Test + public void testSearchReturnsNoProfileForNormalType() throws Exception { + ourServlet.setAddProfileTag(AddProfileTagEnum.ONLY_FOR_CUSTOM); + ourReturnExtended=false; + + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.getEntries().size()); + + BundleEntry entry = bundle.getEntries().get(0); + List profileTags = entry.getCategories().getTagsWithScheme(Tag.HL7_ORG_PROFILE_TAG); + assertEquals(0, profileTags.size()); + } + @Test + public void testSearchReturnsNoProfileForExtendedType() throws Exception { + ourServlet.setAddProfileTag(AddProfileTagEnum.NEVER); + ourReturnExtended=true; + + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.getEntries().size()); + + BundleEntry entry = bundle.getEntries().get(0); + List profileTags = entry.getCategories().getTagsWithScheme(Tag.HL7_ORG_PROFILE_TAG); + assertEquals(0, profileTags.size()); + } + + + @Test + public void testSearchReturnsProfileForNormalType() throws Exception { + ourServlet.setAddProfileTag(AddProfileTagEnum.ALWAYS); + ourReturnExtended=false; + + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.getEntries().size()); + + BundleEntry entry = bundle.getEntries().get(0); + List profileTags = entry.getCategories().getTagsWithScheme(Tag.HL7_ORG_PROFILE_TAG); + assertEquals(1, profileTags.size()); + assertEquals("http://hl7.org/fhir/profiles/Patient", profileTags.get(0).getTerm()); + + Patient p = (Patient) bundle.getEntries().get(0).getResource(); + assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString()); + + } @AfterClass public static void afterClass() throws Exception { @@ -87,11 +140,11 @@ public class CustomTypeTest { DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); ServletHandler proxyHandler = new ServletHandler(); - RestfulServer servlet = new RestfulServer(); - servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); + ourServlet = new RestfulServer(); + ourServlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); - servlet.setResourceProviders(patientProvider); - ServletHolder servletHolder = new ServletHolder(servlet); + ourServlet.setResourceProviders(patientProvider); + ServletHolder servletHolder = new ServletHolder(ourServlet); proxyHandler.addServletWithMapping(servletHolder, "/*"); ourServer.setHandler(proxyHandler); ourServer.start(); @@ -111,6 +164,8 @@ public class CustomTypeTest { } + private static boolean ourReturnExtended = false; + /** * Created by dsotnikov on 2/25/2014. */ @@ -120,7 +175,7 @@ public class CustomTypeTest { public List findPatient(@OptionalParam(name = "_id") StringParam theParam) { ArrayList retVal = new ArrayList(); - ExtendedPatient patient = new ExtendedPatient(); + Patient patient = ourReturnExtended ? new ExtendedPatient() : new Patient(); patient.setId("1"); patient.addIdentifier("system", "identifier123"); if (theParam != null) { diff --git a/hapi-fhir-base/src/test/resources/example-patient-general.json b/hapi-fhir-base/src/test/resources/example-patient-general.json index 4678e29a85d..69e05527191 100644 --- a/hapi-fhir-base/src/test/resources/example-patient-general.json +++ b/hapi-fhir-base/src/test/resources/example-patient-general.json @@ -143,306 +143,7 @@ } ], "managingOrganization":{ - "resource":"Organization/1" - }, - "active":true -}13:16:59.119 [main] INFO ca.uhn.fhir.parser.JsonParserTest - -{ - "resourceType":"Patient", - "extension":[ - { - "url":"urn:patientext:att", - "valueAttachment":{ - "contentType":"aaaa", - "data":"AAAA" - } - }, - { - "url":"urn:patientext:moreext", - "extension":[ - { - "url":"urn:patientext:moreext:1", - "valuestring":"str1" - }, - { - "url":"urn:patientext:moreext:2", - "valuestring":"str2" - } - ] - } - ], - "modifierExtension":[ - { - "url":"urn:modext", - "valuedate":"2011-01-02" - } - ], - "text":{ - "status":"generated", - "div":"
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NamePeter James Chalmers (\"Jim\")
Address534 Erewhon, Pleasantville, Vic, 3999
ContactsHome: unknown. Work: (03) 5555 6473
IdMRN: 12345 (Acme Healthcare)
\n
" - }, - "identifier":[ - { - "use":"usual", - "label":"MRN", - "system":"urn:oid:1.2.36.146.595.217.0.1", - "value":"12345", - "period":{ - "start":"2001-05-06" - }, - "assigner":{ - "display":"Acme Healthcare" - } - } - ], - "name":[ - { - "use":"official", - "family":[ - "Chalmers" - ], - "given":[ - "Peter", - "James" - ] - }, - { - "use":"usual", - "given":[ - "Jim" - ] - } - ], - "telecom":[ - { - "use":"home" - }, - { - "system":"phone", - "value":"(03) 5555 6473", - "use":"work" - } - ], - "gender":{ - "coding":[ - { - "system":"http://hl7.org/fhir/v3/AdministrativeGender", - "code":"M", - "display":"Male" - } - ] - }, - "birthDate":"1974-12-25", - "deceasedBoolean":false, - "address":[ - { - "use":"home", - "line":[ - "534 Erewhon St" - ], - "city":"PleasantVille", - "state":"Vic", - "zip":"3999" - }, - { - "use":"old", - "line":[ - "SecondAddress" - ] - } - ], - "contact":[ - { - "relationship":[ - { - "coding":[ - { - "system":"http://hl7.org/fhir/patient-contact-relationship", - "code":"partner" - } - ] - } - ], - "name":{ - "family":[ - "du", - "March??" - ], - "_family":[ - { - "extension":[ - { - "url":"http://hl7.org/fhir/Profile/iso-21090#qualifier", - "valuecode":"VV" - } - ] - }, - null - ], - "given":[ - "B??n??dicte" - ] - }, - "telecom":[ - { - "system":"phone", - "value":"+33 (237) 998327" - } - ] - } - ], - "managingOrganization":{ - "resource":"Organization/1" - }, - "active":true -} -13:16:59.122 [main] INFO ca.uhn.fhir.parser.JsonParserTest - -{ - "resourceType":"Patient", - "extension":[ - { - "url":"urn:patientext:att", - "valueAttachment":{ - "contentType":"aaaa", - "data":"AAAA" - } - }, - { - "url":"urn:patientext:moreext", - "extension":[ - { - "url":"urn:patientext:moreext:1", - "valuestring":"str1" - }, - { - "url":"urn:patientext:moreext:2", - "valuestring":"str2" - } - ] - } - ], - "modifierExtension":[ - { - "url":"urn:modext", - "valuedate":"2011-01-02" - } - ], - "text":{ - "status":"generated", - "div":"
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NamePeter James Chalmers (\"Jim\")
Address534 Erewhon, Pleasantville, Vic, 3999
ContactsHome: unknown. Work: (03) 5555 6473
IdMRN: 12345 (Acme Healthcare)
\n
" - }, - "identifier":[ - { - "use":"usual", - "label":"MRN", - "system":"urn:oid:1.2.36.146.595.217.0.1", - "value":"12345", - "period":{ - "start":"2001-05-06" - }, - "assigner":{ - "display":"Acme Healthcare" - } - } - ], - "name":[ - { - "use":"official", - "family":[ - "Chalmers" - ], - "given":[ - "Peter", - "James" - ] - }, - { - "use":"usual", - "given":[ - "Jim" - ] - } - ], - "telecom":[ - { - "use":"home" - }, - { - "system":"phone", - "value":"(03) 5555 6473", - "use":"work" - } - ], - "gender":{ - "coding":[ - { - "system":"http://hl7.org/fhir/v3/AdministrativeGender", - "code":"M", - "display":"Male" - } - ] - }, - "birthDate":"1974-12-25", - "deceasedBoolean":false, - "address":[ - { - "use":"home", - "line":[ - "534 Erewhon St" - ], - "city":"PleasantVille", - "state":"Vic", - "zip":"3999" - }, - { - "use":"old", - "line":[ - "SecondAddress" - ] - } - ], - "contact":[ - { - "relationship":[ - { - "coding":[ - { - "system":"http://hl7.org/fhir/patient-contact-relationship", - "code":"partner" - } - ] - } - ], - "name":{ - "family":[ - "du", - "Marché" - ], - "_family":[ - { - "extension":[ - { - "url":"http://hl7.org/fhir/Profile/iso-21090#qualifier", - "valuecode":"VV" - } - ] - }, - null - ], - "given":[ - "Bénédicte" - ] - }, - "telecom":[ - { - "system":"phone", - "value":"+33 (237) 998327" - } - ] - } - ], - "managingOrganization":{ - "resource":"Organization/1" + "reference":"Organization/1" }, "active":true } \ No newline at end of file diff --git a/hapi-fhir-mitreid-integration/.settings/org.eclipse.core.resources.prefs b/hapi-fhir-mitreid-integration/.settings/org.eclipse.core.resources.prefs index 99f26c0203a..f9fe34593fc 100644 --- a/hapi-fhir-mitreid-integration/.settings/org.eclipse.core.resources.prefs +++ b/hapi-fhir-mitreid-integration/.settings/org.eclipse.core.resources.prefs @@ -1,2 +1,4 @@ eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 encoding/=UTF-8 diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java index 9fc2988ee7d..45fa90821ff 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java @@ -65,6 +65,7 @@ import ca.uhn.fhir.rest.gclient.ICreateTyped; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.gclient.IUntypedQuery; import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -967,11 +968,14 @@ public class Controller { } List values; + boolean addToWhere=true; if ("token".equals(nextType)) { if (isBlank(parts.get(2))) { return true; } values = Collections.singletonList(StringUtils.join(parts, "")); + addToWhere=false; + theQuery.where(new TokenClientParam(nextName + nextQualifier).exactly().systemAndCode(parts.get(0), parts.get(2))); } else if ("date".equals(nextType)) { values = new ArrayList(); if (isNotBlank(parts.get(1))) { @@ -998,8 +1002,9 @@ public class Controller { theClientCodeJsonWriter.write("qualifier", nextQualifier); theClientCodeJsonWriter.write("value", nextValue); theClientCodeJsonWriter.writeEnd(); - + if (addToWhere) { theQuery.where(new StringClientParam(nextName + nextQualifier).matches().value(nextValue)); + } } diff --git a/hapi-tinder-plugin/.settings/org.eclipse.core.resources.prefs b/hapi-tinder-plugin/.settings/org.eclipse.core.resources.prefs index 989609020a0..29abf999564 100644 --- a/hapi-tinder-plugin/.settings/org.eclipse.core.resources.prefs +++ b/hapi-tinder-plugin/.settings/org.eclipse.core.resources.prefs @@ -1,5 +1,6 @@ eclipse.preferences.version=1 encoding//src/main/java=UTF-8 encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 encoding//src/test/resources=UTF-8 encoding/=UTF-8