Let server return profile info if configured to do so

This commit is contained in:
jamesagnew 2014-08-21 00:34:19 -04:00
parent 731d369be0
commit ab17c9f3d1
12 changed files with 591 additions and 684 deletions

View File

@ -629,8 +629,6 @@ class ModelScanner {
} }
} }
String profile = resourceDefinition.profile();
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(resourceName, theClass, resourceDefinition); RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(resourceName, theClass, resourceDefinition);
myClassToElementDefinitions.put(theClass, resourceDef); myClassToElementDefinitions.put(theClass, resourceDef);
if (primaryNameProvider) { if (primaryNameProvider) {

View File

@ -352,4 +352,8 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
return retVal; return retVal;
} }
public boolean isStandardProfile() {
return myResourceProfile.startsWith("http://hl7.org/fhir/profiles");
}
} }

View File

@ -211,6 +211,7 @@ public abstract class BaseClient {
} }
String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase();
OperationOutcome oo=null;
if (Constants.CT_TEXT.equals(mimeType)) { if (Constants.CT_TEXT.equals(mimeType)) {
message = message + ": " + body; message = message + ": " + body;
} else { } else {
@ -218,7 +219,7 @@ public abstract class BaseClient {
if (enc != null) { if (enc != null) {
IParser p = enc.newParser(theContext); IParser p = enc.newParser(theContext);
try { try {
OperationOutcome oo = p.parseResource(OperationOutcome.class, body); oo = p.parseResource(OperationOutcome.class, body);
if (oo.getIssueFirstRep().getDetails().isEmpty()==false) { if (oo.getIssueFirstRep().getDetails().isEmpty()==false) {
message = message + ": " + oo.getIssueFirstRep().getDetails().getValue(); message = message + ": " + oo.getIssueFirstRep().getDetails().getValue();
} }
@ -231,6 +232,7 @@ public abstract class BaseClient {
keepResponseAndLogIt(theLogRequestAndResponse, response, body); keepResponseAndLogIt(theLogRequestAndResponse, response, body);
BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), message); BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), message);
exception.setOperationOutcome(oo);
if (body != null) { if (body != null) {
exception.setResponseBody(body); exception.setResponseBody(body);

View File

@ -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
}

View File

@ -55,7 +55,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle; 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.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.Tag;
@ -87,6 +86,7 @@ public class RestfulServer extends HttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private AddProfileTagEnum myAddProfileTag;
private FhirContext myFhirContext; private FhirContext myFhirContext;
private String myImplementationDescription; private String myImplementationDescription;
private ResourceBinding myNullResourceBinding = new ResourceBinding(); 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. * This method is called prior to sending a response to incoming requests. It is used to add custom headers.
* <p> * <p>
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality. * Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* inadvertantly disabling functionality.
* </p> * </p>
*/ */
public void addHeadersToResponse(HttpServletResponse theHttpResponse) { public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-Powered-By", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server"); 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<Object> getPlainProviders() {
return myPlainProviders;
}
public Collection<ResourceBinding> getResourceBindings() {
return myResourceNameToProvider.values();
}
/**
* Provides the resource providers for this server
*/
public Collection<IResourceProvider> getResourceProviders() {
return myResourceProviders;
}
/**
* Provides the security manager, or <code>null</code> 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.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* </p>
*/
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<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) {
Map<Class<? extends IResource>, IResourceProvider> typeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
for (IResourceProvider nextProvider : resourceProvider) {
Class<? extends IResource> 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<Object> 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 <code>null</code> 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<Object> 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<IResourceProvider> theResourceProviders) {
myResourceProviders = theResourceProviders;
}
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders = Arrays.asList(theResourceProviders);
}
/**
* Sets the security manager, or <code>null</code> 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.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* </p>
* 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 <code>true</code> (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 { private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) { if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class ust be public"); throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class ust be public");
} }
} }
@Override /**
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { * Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response); */
} private int escapedLength(String theServletPath) {
int delta = 0;
@Override for (int i = 0; i < theServletPath.length(); i++) {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { char next = theServletPath.charAt(i);
handleRequest(SearchMethodBinding.RequestType.GET, request, response); if (next == ' ') {
} delta = delta + 2;
}
@Override }
protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException { return theServletPath.length() + delta;
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);
} }
private void findResourceMethods(Object theProvider) throws Exception { 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 <code>null</code> (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 { private int findResourceMethods(Object theProvider, Class<?> clazz) throws ConfigurationException {
int count = 0; 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<Object> getPlainProviders() {
return myPlainProviders;
}
public Collection<ResourceBinding> getResourceBindings() {
return myResourceNameToProvider.values();
}
/**
* Provides the resource providers for this server
*/
public Collection<IResourceProvider> getResourceProviders() {
return myResourceProviders;
}
/**
* Provides the security manager, or <code>null</code> 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.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
* </p>
*/
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 { private void handlePagingRequest(Request theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException {
IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction); IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction);
if (resultList == null) { if (resultList == null) {
@ -361,11 +572,40 @@ public class RestfulServer extends HttpServlet {
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest()); boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest); NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
boolean respondGzip = theRequest.isRespondGzip(); boolean respondGzip = theRequest.isRespondGzip();
streamResponseAsBundle(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, streamResponseAsBundle(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction, respondGzip);
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 { protected void handleRequest(SearchMethodBinding.RequestType theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
String fhirServerBase = null; String fhirServerBase = null;
try { try {
@ -547,7 +787,7 @@ public class RestfulServer extends HttpServlet {
} catch (Throwable e) { } catch (Throwable e) {
OperationOutcome oo=null; OperationOutcome oo = null;
int statusCode = 500; int statusCode = 500;
if (e instanceof BaseServerResponseException) { if (e instanceof BaseServerResponseException) {
@ -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) * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
*/ * server being used.
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<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) {
Map<Class<? extends IResource>, IResourceProvider> typeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
for (IResourceProvider nextProvider : resourceProvider) {
Class<? extends IResource> 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<Object> 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.
*/ */
protected void initialize() throws ServletException { protected void initialize() throws ServletException {
// nothing by default // 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 <code>null</code> 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 <code>null</code> (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<Object> 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<IResourceProvider> theResourceProviders) {
myResourceProviders = theResourceProviders;
}
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders = Arrays.asList(theResourceProviders);
}
/**
* Sets the security manager, or <code>null</code> 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.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
* </p>
* 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 <code>true</code> (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<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults) { public static Bundle createBundleFromResourceList(FhirContext theContext, String theAuthor, List<IResource> theResult, String theServerBase, String theCompleteUrl, int theTotalResults) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.getAuthorName().setValue(theAuthor); bundle.getAuthorName().setValue(theAuthor);
@ -857,7 +892,6 @@ public class RestfulServer extends HttpServlet {
} while (references.isEmpty() == false); } while (references.isEmpty() == false);
bundle.addResource(next, theContext, theServerBase); bundle.addResource(next, theContext, theServerBase);
// addProfileToBundleEntry(theContext, next, entry);
} }
@ -866,26 +900,12 @@ public class RestfulServer extends HttpServlet {
*/ */
for (IResource next : addedResources) { for (IResource next : addedResources) {
bundle.addResource(next, theContext, theServerBase); bundle.addResource(next, theContext, theServerBase);
// addProfileToBundleEntry(theContext, next, entry);
} }
bundle.getTotalResults().setValue(theTotalResults); bundle.getTotalResults().setValue(theTotalResults);
return bundle; return bundle;
} }
/*
private static void addProfileToBundleEntry(FhirContext theContext, IResource next, BundleEntry entry) {
List<Tag> 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) { public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(theServerBase); b.append(theServerBase);
@ -1012,17 +1032,6 @@ public class RestfulServer extends HttpServlet {
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS); 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) { public static boolean prettyPrintResponse(Request theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters(); Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY); String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
@ -1048,9 +1057,8 @@ public class RestfulServer extends HttpServlet {
return prettyPrint; return prettyPrint;
} }
public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId, boolean theRespondGzip) NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId, boolean theRespondGzip) throws IOException {
throws IOException {
assert !theServerBase.endsWith("/"); assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200); 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 bundle = createBundleFromResourceList(theServer.getFhirContext(), theServer.getServerName(), resourceList, theServerBase, theCompleteUrl, theResult.size());
bundle.setPublished(theResult.getPublished()); 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, public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip,
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase) throws IOException { String theServerBase) throws IOException {
int stausCode = 200; int stausCode = 200;
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase); 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, private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource) {
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip, String theServerBase) throws IOException {
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); theHttpResponse.setStatus(stausCode);
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) { 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); 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) { if (theResource instanceof Binary) {
Binary bin = (Binary) theResource; Binary bin = (Binary) theResource;
if (isNotBlank(bin.getContentType())) { if (isNotBlank(bin.getContentType())) {

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; 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.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Child;
@ -432,7 +433,7 @@ public class JsonParserTest {
ourLog.info(str); ourLog.info(str);
assertThat(str, StringContains.containsString("<div>AAA</div>")); assertThat(str, StringContains.containsString("<div>AAA</div>"));
String substring = "\"resource\":\"#"; String substring = "\"reference\":\"#";
assertThat(str, StringContains.containsString(substring)); assertThat(str, StringContains.containsString(substring));
int idx = str.indexOf(substring) + substring.length(); int idx = str.indexOf(substring) + substring.length();
@ -473,7 +474,7 @@ public class JsonParserTest {
String val = parser.encodeResourceToString(patient); String val = parser.encodeResourceToString(patient);
ourLog.info(val); 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); MyPatientWithOneDeclaredExtension actual = parser.parseResource(MyPatientWithOneDeclaredExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
@ -526,7 +527,7 @@ public class JsonParserTest {
String val = parser.encodeResourceToString(patient); String val = parser.encodeResourceToString(patient);
ourLog.info(val); 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); Patient actual = parser.parseResource(Patient.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum()); 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 @Test
public void testSimpleResourceEncodeWithCustomType() throws IOException { public void testSimpleResourceEncodeWithCustomType() throws IOException {

View File

@ -1,10 +1,7 @@
package ca.uhn.fhir.rest.client; package ca.uhn.fhir.rest.client;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.StringReader; import java.io.StringReader;
import java.nio.charset.Charset; 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.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt; 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.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 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"))); when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
try { try {
client.read(Patient.class, new IdDt("Patient/1234")); client.read(Patient.class, new IdDt("Patient/1234"));
fail(); fail();
} catch (InternalErrorException e) { } catch (InternalErrorException e) {
assertThat(e.getMessage(), StringContains.containsString("HTTP 500 Internal Error")); assertThat(e.getMessage(), StringContains.containsString("HTTP 500 Internal Error"));
assertThat(e.getMessage(), StringContains.containsString("Help I'm a bug")); 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<HttpUriRequest> 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);
}
} }

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -8,14 +8,10 @@ import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse; 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.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; 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.IResource;
import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.annotation.ResourceDef; 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.model.dstu.resource.Patient;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.annotation.OptionalParam; 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.annotation.Search;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.testutil.RandomServerPortProvider; import ca.uhn.fhir.testutil.RandomServerPortProvider;
/** /**
@ -50,10 +41,14 @@ public class CustomTypeTest {
private static FhirContext ourCtx = new FhirContext(ExtendedPatient.class); private static FhirContext ourCtx = new FhirContext(ExtendedPatient.class);
private static int ourPort; private static int ourPort;
private static Server ourServer; private static Server ourServer;
private static RestfulServer ourServlet;
@Test @Test
public void testSearchReturnsProfile() throws Exception { public void testSearchReturnsProfile() throws Exception {
ourServlet.setAddProfileTag(AddProfileTagEnum.ONLY_FOR_CUSTOM);
ourReturnExtended=true;
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa");
HttpResponse status = ourClient.execute(httpGet); HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent()); 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<Tag> 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<Tag> 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<Tag> 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 @AfterClass
public static void afterClass() throws Exception { public static void afterClass() throws Exception {
@ -87,11 +140,11 @@ public class CustomTypeTest {
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider(); DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler(); ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(); ourServlet = new RestfulServer();
servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); ourServlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
servlet.setResourceProviders(patientProvider); ourServlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet); ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*"); proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler); ourServer.setHandler(proxyHandler);
ourServer.start(); ourServer.start();
@ -111,6 +164,8 @@ public class CustomTypeTest {
} }
private static boolean ourReturnExtended = false;
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
@ -120,7 +175,7 @@ public class CustomTypeTest {
public List<Patient> findPatient(@OptionalParam(name = "_id") StringParam theParam) { public List<Patient> findPatient(@OptionalParam(name = "_id") StringParam theParam) {
ArrayList<Patient> retVal = new ArrayList<Patient>(); ArrayList<Patient> retVal = new ArrayList<Patient>();
ExtendedPatient patient = new ExtendedPatient(); Patient patient = ourReturnExtended ? new ExtendedPatient() : new Patient();
patient.setId("1"); patient.setId("1");
patient.addIdentifier("system", "identifier123"); patient.addIdentifier("system", "identifier123");
if (theParam != null) { if (theParam != null) {

View File

@ -143,306 +143,7 @@
} }
], ],
"managingOrganization":{ "managingOrganization":{
"resource":"Organization/1" "reference":"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":"<div xmlns=\"http://www.w3.org/1999/xhtml\">\n <table>\n <tbody>\n <tr>\n <td>Name</td>\n <td>Peter James <b>Chalmers</b> (\"Jim\")</td>\n </tr>\n <tr>\n <td>Address</td>\n <td>534 Erewhon, Pleasantville, Vic, 3999</td>\n </tr>\n <tr>\n <td>Contacts</td>\n <td>Home: unknown. Work: (03) 5555 6473</td>\n </tr>\n <tr>\n <td>Id</td>\n <td>MRN: 12345 (Acme Healthcare)</td>\n </tr>\n </tbody>\n </table>\n </div>"
},
"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":"<div xmlns=\"http://www.w3.org/1999/xhtml\">\n <table>\n <tbody>\n <tr>\n <td>Name</td>\n <td>Peter James <b>Chalmers</b> (\"Jim\")</td>\n </tr>\n <tr>\n <td>Address</td>\n <td>534 Erewhon, Pleasantville, Vic, 3999</td>\n </tr>\n <tr>\n <td>Contacts</td>\n <td>Home: unknown. Work: (03) 5555 6473</td>\n </tr>\n <tr>\n <td>Id</td>\n <td>MRN: 12345 (Acme Healthcare)</td>\n </tr>\n </tbody>\n </table>\n </div>"
},
"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 "active":true
} }

View File

@ -1,2 +1,4 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8 encoding/<project>=UTF-8

View File

@ -65,6 +65,7 @@ import ca.uhn.fhir.rest.gclient.ICreateTyped;
import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.gclient.IUntypedQuery; import ca.uhn.fhir.rest.gclient.IUntypedQuery;
import ca.uhn.fhir.rest.gclient.StringClientParam; 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.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -967,11 +968,14 @@ public class Controller {
} }
List<String> values; List<String> values;
boolean addToWhere=true;
if ("token".equals(nextType)) { if ("token".equals(nextType)) {
if (isBlank(parts.get(2))) { if (isBlank(parts.get(2))) {
return true; return true;
} }
values = Collections.singletonList(StringUtils.join(parts, "")); 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)) { } else if ("date".equals(nextType)) {
values = new ArrayList<String>(); values = new ArrayList<String>();
if (isNotBlank(parts.get(1))) { if (isNotBlank(parts.get(1))) {
@ -998,8 +1002,9 @@ public class Controller {
theClientCodeJsonWriter.write("qualifier", nextQualifier); theClientCodeJsonWriter.write("qualifier", nextQualifier);
theClientCodeJsonWriter.write("value", nextValue); theClientCodeJsonWriter.write("value", nextValue);
theClientCodeJsonWriter.writeEnd(); theClientCodeJsonWriter.writeEnd();
if (addToWhere) {
theQuery.where(new StringClientParam(nextName + nextQualifier).matches().value(nextValue)); theQuery.where(new StringClientParam(nextName + nextQualifier).matches().value(nextValue));
}
} }

View File

@ -1,5 +1,6 @@
eclipse.preferences.version=1 eclipse.preferences.version=1
encoding//src/main/java=UTF-8 encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8 encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
encoding//src/test/resources=UTF-8 encoding//src/test/resources=UTF-8
encoding/<project>=UTF-8 encoding/<project>=UTF-8