Lots of tester enhancements

This commit is contained in:
jamesagnew 2014-07-02 08:57:07 -04:00
parent f87c50b4b4
commit 14befcd091
202 changed files with 4333 additions and 6542 deletions

View File

@ -22,14 +22,15 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="owner.project.facets" value="java"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>

View File

@ -5,6 +5,11 @@
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
@ -15,9 +20,17 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
</natures>
</projectDescription>

View File

@ -1,5 +1,13 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="hapi-fhir-base">
<wb-resource deploy-path="/" source-path="/src/main/java"/>
<wb-resource deploy-path="/" source-path="/src/main/resources"/>
</wb-module>
</project-modules>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<installed facet="jst.utility" version="1.0"/>
<installed facet="java" version="1.6"/>
</faceted-project>

View File

@ -55,7 +55,7 @@
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>2.1.3.RELEASE</version>
<version>${thymeleaf-version}</version>
<optional>true</optional>
</dependency>
@ -101,6 +101,13 @@
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring_version}</version>
<optional>true</optional>
</dependency>
<!-- Server -->
<dependency>
<groupId>javax.servlet</groupId>
@ -381,7 +388,9 @@
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-1395874-5', 'sourceforge.net');
ga('create', 'UA-1395874-5', 'auto');
ga('require', 'displayfeatures');
ga('require', 'linkid', 'linkid.js');
ga('send', 'pageview');
</script>

View File

@ -60,6 +60,9 @@
Don't fail on narrative blocks in JSON resources with only an XML declaration but no content (these are
produced by the Health Intersections server)
</action>
<action type="fix">
Server now automatically compresses responses if the client indicates support
</action>
</release>
</body>
</document>

View File

@ -66,7 +66,7 @@ public class FhirContext {
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
/**
* Default constructor. In most cases this is the right constructor to use.
*/
@ -77,7 +77,7 @@ public class FhirContext {
public FhirContext(Class<? extends IResource> theResourceType) {
this(toCollection(theResourceType));
}
public FhirContext(Class<?>... theResourceTypes) {
this(toCollection(theResourceTypes));
}
@ -92,10 +92,6 @@ public class FhirContext {
public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IElement> theElementType) {
return myClassToElementDefinition.get(theElementType);
}
public FhirTerser newTerser() {
return new FhirTerser(this);
}
public INarrativeGenerator getNarrativeGenerator() {
return myNarrativeGenerator;
@ -111,7 +107,7 @@ public class FhirContext {
}
return retVal;
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library.
*/
@ -226,6 +222,10 @@ public class FhirContext {
return getRestfulClientFactory().newGenericClient(theServerBase);
}
public FhirTerser newTerser() {
return new FhirTerser(this);
}
/**
* Create and return a new JSON parser.
*
@ -241,6 +241,10 @@ public class FhirContext {
myNarrativeGenerator = theNarrativeGenerator;
}
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
myRestfulClientFactory = theRestfulClientFactory;
}
private RuntimeResourceDefinition scanResourceType(Class<? extends IResource> theResourceType) {
ArrayList<Class<? extends IResource>> resourceTypes = new ArrayList<Class<? extends IResource>>();
resourceTypes.add(theResourceType);
@ -249,7 +253,7 @@ public class FhirContext {
}
private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IResource>> theResourceTypes) {
ModelScanner scanner = new ModelScanner(theResourceTypes);
ModelScanner scanner = new ModelScanner(myClassToElementDefinition, theResourceTypes);
if (myRuntimeChildUndeclaredExtensionDefinition == null) {
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
}
@ -273,6 +277,11 @@ public class FhirContext {
return classToElementDefinition;
}
/** For unit tests only */
int getElementDefinitionCount() {
return myClassToElementDefinition.size();
}
private static Collection<Class<? extends IResource>> toCollection(Class<? extends IResource> theResourceType) {
ArrayList<Class<? extends IResource>> retVal = new ArrayList<Class<? extends IResource>>(1);
retVal.add(theResourceType);

View File

@ -35,6 +35,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
@ -90,11 +91,15 @@ class ModelScanner {
ModelScanner(Class<? extends IResource> theResourceTypes) throws ConfigurationException {
Set<Class<? extends IElement>> singleton = new HashSet<Class<? extends IElement>>();
singleton.add(theResourceTypes);
init(singleton);
init(null,singleton);
}
ModelScanner(Collection<Class<? extends IResource>> theResourceTypes) throws ConfigurationException {
init(new HashSet<Class<? extends IElement>>(theResourceTypes));
init(null, new HashSet<Class<? extends IElement>>(theResourceTypes));
}
ModelScanner(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Collection<Class<? extends IResource>> theResourceTypes) throws ConfigurationException {
init(theExistingDefinitions, new HashSet<Class<? extends IElement>>(theResourceTypes));
}
public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
@ -147,7 +152,12 @@ class ModelScanner {
}
}
private void init(Set<Class<? extends IElement>> toScan) {
private void init(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Set<Class<? extends IElement>> toScan) {
if (theExistingDefinitions!=null) {
myClassToElementDefinitions.putAll(theExistingDefinitions);
}
int startSize = myClassToElementDefinitions.size();
long start = System.currentTimeMillis();
InputStream str = ModelScanner.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/model.properties");
@ -199,7 +209,11 @@ class ModelScanner {
myScanAlso.clear();
} while (!toScan.isEmpty());
for (BaseRuntimeElementDefinition<?> next : myClassToElementDefinitions.values()) {
for (Entry<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> nextEntry : myClassToElementDefinitions.entrySet()) {
if (theExistingDefinitions!=null&&theExistingDefinitions.containsKey(nextEntry.getKey())) {
continue;
}
BaseRuntimeElementDefinition<?> next = nextEntry.getValue();
next.sealAndInitialize(myClassToElementDefinitions);
}
@ -207,7 +221,8 @@ class ModelScanner {
myRuntimeChildUndeclaredExtensionDefinition.sealAndInitialize(myClassToElementDefinitions);
long time = System.currentTimeMillis() - start;
ourLog.info("Done scanning FHIR library, found {} model entries in {}ms", myClassToElementDefinitions.size(), time);
int size = myClassToElementDefinitions.size()- startSize;
ourLog.info("Done scanning FHIR library, found {} model entries in {}ms", size, time);
}
private void scan(Class<? extends IElement> theClass) throws ConfigurationException {

View File

@ -30,7 +30,6 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public abstract class ResourceMetadataKeyEnum<T> {
/**
* If present and populated with a date/time (as an instance of {@link InstantDt}),
@ -99,8 +98,7 @@ public abstract class ResourceMetadataKeyEnum<T> {
theResource.getResourceMetadata().put(PUBLISHED, theObject);
}
};
/**
* The value for this key is the list of tags associated with this resource
* <p>
@ -129,7 +127,7 @@ public abstract class ResourceMetadataKeyEnum<T> {
public void put(IResource theResource, TagList theObject) {
theResource.getResourceMetadata().put(TAG_LIST, theObject);
}
};
};
/**
@ -153,7 +151,8 @@ public abstract class ResourceMetadataKeyEnum<T> {
public void put(IResource theResource, InstantDt theObject) {
theResource.getResourceMetadata().put(UPDATED, theObject);
}
};
};
/**
* The value for this key is the version ID of the resource object.
@ -175,22 +174,14 @@ public abstract class ResourceMetadataKeyEnum<T> {
theResource.getResourceMetadata().put(VERSION_ID, theObject);
}
};
private final String myValue;
private final String myValue;
public ResourceMetadataKeyEnum(String theValue) {
myValue = theValue;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
@ -209,50 +200,63 @@ public abstract class ResourceMetadataKeyEnum<T> {
return true;
}
private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) {
return null;
} else if (retValObj instanceof String) {
if (isNotBlank((String) retValObj)) {
return new IdDt((String) retValObj);
} else {
return null;
}
} else if (retValObj instanceof IdDt) {
if (((IdDt) retValObj).isEmpty()) {
return null;
} else {
return (IdDt) retValObj;
}
} else if (retValObj instanceof Number) {
return new IdDt(((Number)retValObj).toString());
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName());
public abstract T get(IResource theResource);
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
return result;
}
public abstract void put(IResource theResource, T theObject);
@Override
public String toString() {
return myValue;
}
private String name() {
return myValue;
}
private static InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<InstantDt> theKey) {
Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) {
return null;
} else if (retValObj instanceof Date) {
return new InstantDt((Date) retValObj);
} else if (retValObj instanceof InstantDt) {
if (((InstantDt) retValObj).isEmpty()) {
private static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<?> theKey) {
Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) {
return null;
} else {
return (InstantDt) retValObj;
} else if (retValObj instanceof String) {
if (isNotBlank((String) retValObj)) {
return new IdDt((String) retValObj);
} else {
return null;
}
} else if (retValObj instanceof IdDt) {
if (((IdDt) retValObj).isEmpty()) {
return null;
} else {
return (IdDt) retValObj;
}
} else if (retValObj instanceof Number) {
return new IdDt(((Number)retValObj).toString());
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName());
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName());
}
public abstract T get(IResource theResource);
public abstract void put(IResource theResource, T theObject);
private static InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum<?>, Object> theResourceMetadata, ResourceMetadataKeyEnum<InstantDt> theKey) {
Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) {
return null;
} else if (retValObj instanceof Date) {
return new InstantDt((Date) retValObj);
} else if (retValObj instanceof InstantDt) {
if (((InstantDt) retValObj).isEmpty()) {
return null;
} else {
return (InstantDt) retValObj;
}
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName());
}
}

View File

@ -151,6 +151,7 @@ public abstract class BaseClient {
list.add(next.getValue());
}
}
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() > 299) {
String body=null;

View File

@ -28,7 +28,8 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import ca.uhn.fhir.context.ConfigurationException;
@ -38,9 +39,12 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding;
public class RestfulClientFactory implements IRestfulClientFactory {
private int myConnectionRequestTimeout=10000;
private int myConnectTimeout=10000;
private FhirContext myContext;
private HttpClient myHttpClient;
private Map<Class<? extends IRestfulClient>, ClientInvocationHandler> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandler>();
private int mySocketTimeout = 10000;
/**
* Constructor
@ -48,14 +52,63 @@ public class RestfulClientFactory implements IRestfulClientFactory {
* @param theContext
* The context
*/
public RestfulClientFactory(FhirContext theContext) {
public RestfulClientFactory(FhirContext theFhirContext) {
myContext=theFhirContext;
}
/**
* Constructor
*/
public RestfulClientFactory() {
}
/**
* Sets the context associated with this client factory. Must not be called
* more than once.
*/
public void setFhirContext(FhirContext theContext) {
if(myContext!=null&&myContext!=theContext) {
throw new IllegalStateException("RestfulClientFactory instance is already associated with one FhirContext. RestfulClientFactory instances can not be shared.");
}
myContext = theContext;
}
@SuppressWarnings("unchecked")
private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) {
T proxy = (T) Proxy.newProxyInstance(theClientType.getClassLoader(), new Class[] { theClientType }, theInvocationHandler);
return proxy;
public int getConnectionRequestTimeout() {
return myConnectionRequestTimeout;
}
public int getConnectTimeout() {
return myConnectTimeout;
}
@Override
public synchronized HttpClient getHttpClient() {
if (myHttpClient == null) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
//@formatter:off
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setSocketTimeout(mySocketTimeout)
.setConnectTimeout(myConnectTimeout)
.setConnectionRequestTimeout(myConnectionRequestTimeout)
.setStaleConnectionCheckEnabled(true)
.build();
myHttpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(defaultRequestConfig)
.disableCookieManagement()
.build();
//@formatter:on
}
return myHttpClient;
}
public int getSocketTimeout() {
return mySocketTimeout;
}
/**
@ -86,7 +139,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
if (invocationHandler == null) {
invocationHandler = new ClientInvocationHandler(client, myContext, serverBase, theClientType);
for (Method nextMethod : theClientType.getMethods()) {
BaseMethodBinding binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null);
BaseMethodBinding<?> binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null);
invocationHandler.addBinding(nextMethod, binding);
}
myInvocationHandlers.put(theClientType, invocationHandler);
@ -98,14 +151,18 @@ public class RestfulClientFactory implements IRestfulClientFactory {
}
@Override
public synchronized HttpClient getHttpClient() {
if (myHttpClient == null) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
myHttpClient = builder.build();
}
return myHttpClient;
public IGenericClient newGenericClient(String theServerBase) {
return new GenericClient(myContext, getHttpClient(), theServerBase);
}
public synchronized void setConnectionRequestTimeout(int theConnectionRequestTimeout) {
myConnectionRequestTimeout = theConnectionRequestTimeout;
myHttpClient=null;
}
public synchronized void setConnectTimeout(int theConnectTimeout) {
myConnectTimeout = theConnectTimeout;
myHttpClient=null;
}
/**
@ -120,9 +177,15 @@ public class RestfulClientFactory implements IRestfulClientFactory {
myHttpClient = theHttpClient;
}
@Override
public IGenericClient newGenericClient(String theServerBase) {
return new GenericClient(myContext, getHttpClient(), theServerBase);
public synchronized void setSocketTimeout(int theSocketTimeout) {
mySocketTimeout = theSocketTimeout;
myHttpClient=null;
}
@SuppressWarnings("unchecked")
private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) {
T proxy = (T) Proxy.newProxyInstance(theClientType.getClassLoader(), new Class[] { theClientType }, theInvocationHandler);
return proxy;
}
}

View File

@ -260,12 +260,15 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
b.append('/');
b.append(getResourceName());
b.append('/');
b.append(response.getId().getValue());
if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
b.append("/_history/");
b.append(response.getId().getIdPart());
if (response.getId().hasVersionIdPart()) {
b.append("/"+Constants.PARAM_HISTORY+"/");
b.append(response.getId().getVersionIdPart());
}else if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) {
b.append("/"+Constants.PARAM_HISTORY+"/");
b.append(response.getVersionId().getValue());
}
theResponse.addHeader("Location", b.toString());
theResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString());
}
private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) {
@ -396,7 +399,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
}
public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map<String, List<String>> theHeaders) {
List<String> locationHeaders = theHeaders.get("location");
List<String> locationHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC);
MethodOutcome retVal = new MethodOutcome();
if (locationHeaders != null && locationHeaders.size() > 0) {
String locationHeader = locationHeaders.get(0);

View File

@ -218,10 +218,12 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
Integer count = RestfulServer.extractCountParameter(theRequest.getServletRequest());
boolean respondGzip=theRequest.isRespondGzip();
IBundleProvider result = invokeServer(theRequest, params);
switch (getReturnType()) {
case BUNDLE:
RestfulServer.streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, 0, count, null);
RestfulServer.streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, 0, count, null, respondGzip);
break;
case RESOURCE:
if (result.size() == 0) {
@ -229,7 +231,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
} else if (result.size() > 1) {
throw new InternalErrorException("Method returned multiple resources");
}
RestfulServer.streamResponseAsResource(theServer, theResponse, result.getResources(0, 1).get(0), responseEncoding, prettyPrint, requestIsBrowser, narrativeMode);
RestfulServer.streamResponseAsResource(theServer, theResponse, result.getResources(0, 1).get(0), responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip);
break;
}
}

View File

@ -20,7 +20,6 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -48,6 +47,7 @@ public class Request {
private HttpServletResponse myServletResponse;
private IdDt myVersion;
private Map<String,List<String>> myUnqualifiedToQualifiedNames;
private boolean myRespondGzip;
public String getCompleteUrl() {
return myCompleteUrl;
@ -177,4 +177,12 @@ public class Request {
return retVal;
}
public void setRespondGzip(boolean theRespondGzip) {
myRespondGzip=theRespondGzip;
}
public boolean isRespondGzip() {
return myRespondGzip;
}
}

View File

@ -73,18 +73,17 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
@Override
protected void addParametersForServerRequest(Request theRequest, Object[] theParams) {
/*
* We are being a bit lenient here, since technically the client is
* supposed to include the version in the Content-Location header, but
* we allow it in the PUT URL as well..
* We are being a bit lenient here, since technically the client is supposed to include the version in the
* Content-Location header, but we allow it in the PUT URL as well..
*/
String locationHeader = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_LOCATION);
IdDt id = new IdDt(locationHeader);
if (isNotBlank(id.getResourceType())) {
if (!getResourceName().equals(id.getResourceType())) {
throw new InvalidRequestException("Attempting to update '"+getResourceName()+ "' but content-location header specifies different resource type '"+id.getResourceType()+"' - header value: " + locationHeader);
throw new InvalidRequestException("Attempting to update '" + getResourceName() + "' but content-location header specifies different resource type '" + id.getResourceType() + "' - header value: " + locationHeader);
}
}
if (isNotBlank(locationHeader)) {
MethodOutcome mo = new MethodOutcome();
parseContentLocation(mo, getResourceName(), locationHeader);
@ -126,15 +125,24 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
}
public static HttpPutClientInvocation createUpdateInvocation(IResource theResource, IdDt idDt, IdDt versionIdDt, FhirContext context) {
String id = idDt.getValue();
String resourceName = context.getResourceDefinition(theResource).getName();
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
urlExtension.append('/');
urlExtension.append(id);
urlExtension.append(idDt.getIdPart());
HttpPutClientInvocation retVal = new HttpPutClientInvocation(context, theResource, urlExtension.toString());
if (versionIdDt != null) {
if (idDt.hasVersionIdPart()) {
String versionId = versionIdDt.getValue();
if (StringUtils.isNotBlank(versionId)) {
StringBuilder b = new StringBuilder();
b.append('/');
b.append(urlExtension);
b.append("/_history/");
b.append(versionId);
retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString());
}
} else if (versionIdDt != null) {
String versionId = versionIdDt.getValue();
if (StringUtils.isNotBlank(versionId)) {
StringBuilder b = new StringBuilder();
@ -145,7 +153,7 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
retVal.addHeader(Constants.HEADER_CONTENT_LOCATION, b.toString());
}
}
addTagsToPostOrPut(theResource, retVal);
return retVal;
@ -158,7 +166,7 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
if (theRequest.getVersionId() == null) {
return false;
}
}else {
} else {
if (theRequest.getVersionId() != null) {
return false;
}

View File

@ -45,6 +45,7 @@ public class Constants {
public static final String HEADER_ACCEPT = "Accept";
public static final String HEADER_CATEGORY = "Category";
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
public static final String HEADER_CONTENT_LOCATION_LC = HEADER_CONTENT_LOCATION.toLowerCase();
public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_LAST_MODIFIED = "Last-Modified";
public static final String HEADER_LAST_MODIFIED_LOWERCASE = HEADER_LAST_MODIFIED.toLowerCase();
@ -81,6 +82,9 @@ public class Constants {
public static final int STATUS_HTTP_422_UNPROCESSABLE_ENTITY = 422;
public static final int STATUS_HTTP_500_INTERNAL_ERROR = 500;
public static final String URL_TOKEN_HISTORY = "_history";
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
public static final String ENCODING_GZIP = "gzip";
static {

View File

@ -23,8 +23,10 @@ package ca.uhn.fhir.rest.server;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
@ -36,6 +38,7 @@ import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
@ -352,7 +355,8 @@ public class RestfulServer extends HttpServlet {
boolean prettyPrint = prettyPrintResponse(theRequest);
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
streamResponseAsBundle(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction);
boolean respondGzip=theRequest.isRespondGzip();
streamResponseAsBundle(this, theResponse, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, start, count, thePagingAction, respondGzip);
}
@ -488,6 +492,17 @@ public class RestfulServer extends HttpServlet {
// TODO: look for more tokens for version, compartments, etc...
String acceptEncoding = theRequest.getHeader(Constants.HEADER_ACCEPT_ENCODING);
boolean respondGzip=false;
if (acceptEncoding != null) {
String[] parts = acceptEncoding.trim().split("\\s*,\\s*");
for (String string : parts) {
if (string.equals("gzip")) {
respondGzip=true;
}
}
}
Request r = new Request();
r.setResourceName(resourceName);
r.setId(id);
@ -500,6 +515,7 @@ public class RestfulServer extends HttpServlet {
r.setCompleteUrl(completeUrl);
r.setServletRequest(theRequest);
r.setServletResponse(theResponse);
r.setRespondGzip(respondGzip);
String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION);
if (getPagingProvider() != null && isNotBlank(pagingAction)) {
@ -526,10 +542,14 @@ public class RestfulServer extends HttpServlet {
theResponse.setContentType("text/plain");
theResponse.setCharacterEncoding("UTF-8");
theResponse.getWriter().write(e.getMessage());
} catch (BaseServerResponseException e) {
} catch (Throwable e) {
int statusCode = 500;
if (e instanceof InternalErrorException) {
ourLog.error("Failure during REST processing", e);
} else if (e instanceof BaseServerResponseException) {
ourLog.warn("Failure during REST processing: {}", e.toString());
statusCode=((BaseServerResponseException) e).getStatusCode();
} else {
ourLog.warn("Failure during REST processing: {}", e.toString());
}
@ -539,19 +559,15 @@ public class RestfulServer extends HttpServlet {
issue.getSeverity().setValueAsEnum(IssueSeverityEnum.ERROR);
issue.getDetails().setValue(e.toString() + "\n\n" + ExceptionUtils.getStackTrace(e));
streamResponseAsResource(this, theResponse, oo, determineResponseEncoding(theRequest), true, false, NarrativeModeEnum.NORMAL, e.getStatusCode());
theResponse.setStatus(e.getStatusCode());
streamResponseAsResource(this, theResponse, oo, determineResponseEncoding(theRequest), true, false, NarrativeModeEnum.NORMAL, statusCode,false);
theResponse.setStatus(statusCode);
addHeadersToResponse(theResponse);
theResponse.setContentType("text/plain");
theResponse.setCharacterEncoding("UTF-8");
theResponse.getWriter().append(e.getMessage());
theResponse.getWriter().close();
} catch (Throwable t) {
// TODO: handle this better
ourLog.error("Failed to process invocation", t);
throw new ServletException(t);
}
}
@ -666,7 +682,6 @@ public class RestfulServer extends HttpServlet {
myPlainProviders = theProviders;
}
/**
* Sets the non-resource specific providers which implement method calls on this server.
*
@ -685,7 +700,6 @@ public class RestfulServer extends HttpServlet {
myPlainProviders = Arrays.asList(theProviders);
}
/**
* Sets the resource providers for this server
*/
@ -769,7 +783,7 @@ public class RestfulServer extends HttpServlet {
return bundle;
}
public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount) {
public static String createPagingLink(String theServerBase, String theSearchId, int theOffset, int theCount, EncodingEnum theResponseEncoding, boolean thePrettyPrint) {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
b.append('?');
@ -788,6 +802,16 @@ public class RestfulServer extends HttpServlet {
b.append(Constants.PARAM_COUNT);
b.append('=');
b.append(theCount);
b.append('&');
b.append(Constants.PARAM_FORMAT);
b.append('=');
b.append(theResponseEncoding.getRequestContentType());
if (thePrettyPrint) {
b.append('&');
b.append(Constants.PARAM_PRETTY);
b.append('=');
b.append(Constants.PARAM_PRETTY_VALUE_TRUE);
}
return b.toString();
}
@ -911,7 +935,7 @@ public class RestfulServer extends HttpServlet {
}
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) throws IOException {
NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId, boolean theRespondGzip) throws IOException {
assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200);
@ -964,18 +988,24 @@ public class RestfulServer extends HttpServlet {
Bundle bundle = createBundleFromResourceList(theServer.getFhirContext(), theServer.getServerName(), resourceList, theServerBase, theCompleteUrl, theResult.size());
bundle.setPublished(theResult.getPublished());
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
bundle.getLinkNext().setValue(createPagingLink(theServerBase, searchId, theOffset + numToReturn, numToReturn));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - numToReturn);
bundle.getLinkPrevious().setValue(createPagingLink(theServerBase, searchId, start, numToReturn));
if (theServer.getPagingProvider() != null) {
int limit;
limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize();
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
if (searchId != null) {
if (theOffset + numToReturn < theResult.size()) {
bundle.getLinkNext().setValue(createPagingLink(theServerBase, searchId, theOffset + numToReturn, numToReturn, theResponseEncoding, thePrettyPrint));
}
if (theOffset > 0) {
int start = Math.max(0, theOffset - limit);
bundle.getLinkPrevious().setValue(createPagingLink(theServerBase, searchId, start, limit, theResponseEncoding, thePrettyPrint));
}
}
}
PrintWriter writer = theHttpResponse.getWriter();
Writer writer = getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == NarrativeModeEnum.ONLY) {
for (IResource next : resourceList) {
@ -990,13 +1020,24 @@ public class RestfulServer extends HttpServlet {
}
}
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
int stausCode = 200;
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode);
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) throws IOException {
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, boolean theRespondGzip) throws IOException {
int stausCode = 200;
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip);
}
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip)
throws IOException {
theHttpResponse.setStatus(stausCode);
if (theResource instanceof Binary) {
@ -1041,7 +1082,7 @@ public class RestfulServer extends HttpServlet {
}
}
PrintWriter writer = theHttpResponse.getWriter();
Writer writer = getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == NarrativeModeEnum.ONLY) {
writer.append(theResource.getText().getDiv().getValueAsString());

View File

@ -46,6 +46,7 @@ public abstract class BaseServerResponseException extends RuntimeException {
registerExceptionType(ResourceVersionNotSpecifiedException.STATUS_CODE, ResourceVersionNotSpecifiedException.class);
registerExceptionType(ResourceVersionConflictException.STATUS_CODE, ResourceVersionConflictException.class);
registerExceptionType(UnprocessableEntityException.STATUS_CODE, UnprocessableEntityException.class);
registerExceptionType(ResourceGoneException.STATUS_CODE, ResourceGoneException.class);
}
private final OperationOutcome myOperationOutcome;

View File

@ -0,0 +1,54 @@
package ca.uhn.fhir.rest.server.exceptions;
/*
* #%L
* HAPI FHIR 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%
*/
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.Constants;
/**
* Represents an <b>HTTP 410 Resource Gone</b> response, which gvenerally
* indicates that the resource has been deleted
*/
public class ResourceGoneException extends BaseServerResponseException {
public static final int STATUS_CODE = Constants.STATUS_HTTP_410_GONE;
public ResourceGoneException(IdDt theId) {
super(STATUS_CODE, "Resource " + (theId != null ? theId.getValue() : "") + " is gone/deleted");
}
public ResourceGoneException(Class<? extends IResource> theClass, IdentifierDt thePatientId) {
super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is gone/deleted");
}
public ResourceGoneException(Class<? extends IResource> theClass, IdDt thePatientId) {
super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is gone/deleted");
}
public ResourceGoneException(String theMessage) {
super(STATUS_CODE, theMessage);
}
private static final long serialVersionUID = 1L;
}

View File

@ -1,508 +0,0 @@
package ca.uhn.fhir.rest.server.tester;
/*
* #%L
* HAPI FHIR 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%
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.templateresolver.TemplateResolver;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.client.GenericClient;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class RestfulServerTesterServlet extends HttpServlet {
private static final boolean DEBUGMODE = true;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerTesterServlet.class);
private static final String PUBLIC_TESTER_RESULT_HTML = "/PublicTesterResult.html";
private static final long serialVersionUID = 1L;
private FhirContext myCtx;
private String myServerBase;
private HashMap<String, String> myStaticResources;
private TemplateEngine myTemplateEngine;
private Set<String> myFilterHeaders;
public RestfulServerTesterServlet() {
myStaticResources = new HashMap<String, String>();
myStaticResources.put("jquery-2.1.0.min.js", "text/javascript");
myStaticResources.put("PublicTester.js", "text/javascript");
myStaticResources.put("PublicTester.css", "text/css");
myStaticResources.put("hapi_fhir_banner.png", "image/png");
myStaticResources.put("hapi_fhir_banner_right.png", "image/png");
myStaticResources.put("shCore.js", "text/javascript");
myStaticResources.put("shBrushJScript.js", "text/javascript");
myStaticResources.put("shBrushXml.js", "text/javascript");
myStaticResources.put("shBrushPlain.js", "text/javascript");
myStaticResources.put("shCore.css", "text/css");
myStaticResources.put("shThemeDefault.css", "text/css");
myStaticResources.put("json2.js", "text/javascript");
myStaticResources.put("minify.json.js", "text/javascript");
myCtx = new FhirContext();
}
public FhirContext getFhirContext() {
return myCtx;
}
@Override
public void init(ServletConfig theConfig) throws ServletException {
myTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new ProfileResourceResolver());
myTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
myTemplateEngine.setDialect(dialect);
myTemplateEngine.initialize();
}
public void setServerBase(String theServerBase) {
myServerBase = theServerBase;
}
private RuntimeResourceDefinition getResourceType(HttpServletRequest theReq) throws ServletException {
String resourceName = StringUtils.defaultString(theReq.getParameter("resourceName"));
RuntimeResourceDefinition def = myCtx.getResourceDefinition(resourceName);
if (def == null) {
throw new ServletException("Invalid resourceName: " + resourceName);
}
return def;
}
private void streamResponse(String theResourceName, String theContentType, HttpServletResponse theResp) throws IOException {
InputStream res = RestfulServerTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/" + theResourceName);
theResp.setContentType(theContentType);
IOUtils.copy(res, theResp.getOutputStream());
}
@Override
protected void doGet(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
if (DEBUGMODE) {
myTemplateEngine.getCacheManager().clearAllCaches();
}
try {
ourLog.info("RequestURI: {}", theReq.getPathInfo());
String resName = theReq.getPathInfo().substring(1);
if (myStaticResources.containsKey(resName)) {
streamResponse(resName, myStaticResources.get(resName), theResp);
return;
}
ConformanceClient client = myCtx.newRestfulClient(ConformanceClient.class, myServerBase);
Conformance conformance = client.getConformance();
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
ctx.setVariable("conf", conformance);
ctx.setVariable("base", myServerBase);
ctx.setVariable("jsonEncodedConf", myCtx.newJsonParser().encodeResourceToString(conformance));
addStandardVariables(ctx, theReq.getParameterMap());
theResp.setContentType("text/html");
theResp.setCharacterEncoding("UTF-8");
myTemplateEngine.process(theReq.getPathInfo(), ctx, theResp.getWriter());
} catch (Exception e) {
ourLog.error("Failed to respond", e);
theResp.sendError(500, e.getMessage());
}
}
private void addStandardVariables(WebContext theCtx, Map<String, String[]> theParameterMap) {
addStandardVariable(theCtx, theParameterMap, "configEncoding");
addStandardVariable(theCtx, theParameterMap, "configPretty");
}
private void addStandardVariable(WebContext theCtx, Map<String, String[]> theParameterMap, String key) {
if (theParameterMap.containsKey(key) && theParameterMap.get(key).length > 0) {
theCtx.setVariable(key, theParameterMap.get(key)[0]);
}
}
@Override
protected void doPost(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
if (DEBUGMODE) {
myTemplateEngine.getCacheManager().clearAllCaches();
}
GenericClient client = (GenericClient) myCtx.newRestfulGenericClient(myServerBase);
client.setKeepResponses(true);
boolean returnsResource;
long latency=0;
try {
String method = theReq.getParameter("method");
String prettyParam = theReq.getParameter("configPretty");
if ("on".equals(prettyParam)) {
client.setPrettyPrint(true);
}
if ("xml".equals(theReq.getParameter("configEncoding"))) {
client.setEncoding(EncodingEnum.XML);
} else if ("json".equals(theReq.getParameter("configEncoding"))) {
client.setEncoding(EncodingEnum.JSON);
}
long start = System.currentTimeMillis();
if ("conformance".equals(method)) {
returnsResource = true;
client.conformance();
} else if ("read".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
returnsResource = true;
client.read(def.getImplementingClass(), new IdDt(id));
} else if ("vread".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
String versionId = StringUtils.defaultString(theReq.getParameter("versionid"));
if (StringUtils.isBlank(versionId)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No Version ID specified");
}
returnsResource = true;
client.vread(def.getImplementingClass(), new IdDt(id), new IdDt(versionId));
} else if ("delete".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
returnsResource = false;
client.delete(def.getImplementingClass(), new IdDt(id));
} else if ("history-instance".equals(method) || "history-server".equals(method) || "history-type".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
returnsResource = false;
client.history(def.getImplementingClass(), new IdDt(id),null,null);
} else if ("create".equals(method)) {
IResource resource = parseIncomingResource(theReq, theResp, client);
returnsResource = false;
client.create(resource);
} else if ("validate".equals(method)) {
IResource resource = parseIncomingResource(theReq, theResp, client);
returnsResource = false;
client.validate(resource);
} else if ("update".equals(method)) {
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No ID specified");
}
IResource resource = parseIncomingResource(theReq, theResp, client);
returnsResource = false;
client.update(new IdDt(id), resource);
} else if ("searchType".equals(method)) {
Map<String, List<IQueryParameterType>> params = new HashMap<String, List<IQueryParameterType>>();
HashSet<String> hashSet = new HashSet<String>(theReq.getParameterMap().keySet());
String paramName = null;
IQueryParameterType paramValue = null;
while (hashSet.isEmpty() == false) {
String nextKey = hashSet.iterator().next();
String nextValue = theReq.getParameter(nextKey);
paramName = null;
paramValue = null;
if (nextKey.startsWith("param.token.")) {
int prefixLength = "param.token.".length();
paramName = nextKey.substring(prefixLength + 2);
String systemKey = "param.token." + "1." + paramName;
String valueKey = "param.token." + "2." + paramName;
String system = theReq.getParameter(systemKey);
String value = theReq.getParameter(valueKey);
paramValue = new IdentifierDt(system, value);
hashSet.remove(systemKey);
hashSet.remove(valueKey);
} else if (nextKey.startsWith("param.string.")) {
paramName = nextKey.substring("param.string.".length());
paramValue = new StringDt(nextValue);
}
if (paramName != null) {
if (params.containsKey(paramName) == false) {
params.put(paramName, new ArrayList<IQueryParameterType>());
}
params.get(paramName).add(paramValue);
}
hashSet.remove(nextKey);
}
RuntimeResourceDefinition def = getResourceType(theReq);
returnsResource = false;
client.search(def.getImplementingClass(), params);
} else {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "Invalid method: " + method);
return;
}
latency = System.currentTimeMillis() - start;
} catch (DataFormatException e) {
ourLog.error("Failed to invoke method", e);
returnsResource = false;
} catch (Exception e) {
ourLog.error("Failure during processing", e);
returnsResource = false;
}
try {
HttpRequestBase lastRequest = client.getLastRequest();
String requestBody = null;
String requestSyntaxHighlighterClass = null;
if (lastRequest instanceof HttpEntityEnclosingRequest) {
HttpEntityEnclosingRequest lastEERequest = (HttpEntityEnclosingRequest) lastRequest;
HttpEntity lastEE = lastEERequest.getEntity();
if (lastEE.isRepeatable()) {
StringWriter requestCapture = new StringWriter();
lastEE.writeTo(new WriterOutputStream(requestCapture, "UTF-8"));
requestBody = requestCapture.toString();
ContentType ct = ContentType.get(lastEE);
String mimeType = ct.getMimeType();
EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType);
if (ctEnum == null) {
requestSyntaxHighlighterClass = "brush: plain";
} else {
switch (ctEnum) {
case JSON:
requestSyntaxHighlighterClass = "brush: jscript";
break;
case XML:
default:
requestSyntaxHighlighterClass = "brush: xml";
break;
}
}
}
}
String resultSyntaxHighlighterClass;
String requestUrl = lastRequest != null ? lastRequest.getURI().toASCIIString() : null;
String action = client.getLastRequest() != null ? client.getLastRequest().getMethod() : null;
String resultStatus = client.getLastResponse() != null ? client.getLastResponse().getStatusLine().toString() : null;
String resultBody = client.getLastResponseBody();
HttpResponse lastResponse = client.getLastResponse();
ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null;
String mimeType = ct != null ? ct.getMimeType() : null;
EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType);
String narrativeString = "";
if (ctEnum == null) {
resultSyntaxHighlighterClass = "brush: plain";
} else {
switch (ctEnum) {
case JSON:
resultSyntaxHighlighterClass = "brush: jscript";
if (returnsResource) {
narrativeString = parseNarrative(ctEnum, resultBody);
}
break;
case XML:
default:
resultSyntaxHighlighterClass = "brush: xml";
if (returnsResource) {
narrativeString = parseNarrative(ctEnum, resultBody);
}
break;
}
}
Header[] requestHeaders = lastRequest != null ? applyHeaderFilters(lastRequest.getAllHeaders()) : new Header[0];
Header[] responseHeaders = lastResponse != null ? applyHeaderFilters(lastResponse.getAllHeaders()) : new Header[0];
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
ctx.setVariable("base", myServerBase);
ctx.setVariable("requestUrl", requestUrl);
ctx.setVariable("action", action);
ctx.setVariable("resultStatus", resultStatus);
ctx.setVariable("requestBody", StringEscapeUtils.escapeHtml4(requestBody));
ctx.setVariable("requestSyntaxHighlighterClass", requestSyntaxHighlighterClass);
ctx.setVariable("resultBody", StringEscapeUtils.escapeHtml4(resultBody));
ctx.setVariable("resultSyntaxHighlighterClass", resultSyntaxHighlighterClass);
ctx.setVariable("requestHeaders", requestHeaders);
ctx.setVariable("responseHeaders", responseHeaders);
ctx.setVariable("narrative", narrativeString);
ctx.setVariable("latencyMs", latency);
myTemplateEngine.process(PUBLIC_TESTER_RESULT_HTML, ctx, theResp.getWriter());
} catch (Exception e) {
ourLog.error("Failure during processing", e);
theResp.sendError(500, e.toString());
}
}
private IResource parseIncomingResource(HttpServletRequest theReq, HttpServletResponse theResp, GenericClient theClient) throws ServletException, IOException {
RuntimeResourceDefinition def = getResourceType(theReq);
String resourceText = StringUtils.defaultString(theReq.getParameter("resource"));
if (StringUtils.isBlank(resourceText)) {
theResp.sendError(Constants.STATUS_HTTP_400_BAD_REQUEST, "No resource content specified");
}
IResource resource;
if (theClient.getEncoding() == null) {
if (resourceText.trim().startsWith("{")) {
resource = myCtx.newJsonParser().parseResource(def.getImplementingClass(), resourceText);
} else {
resource = myCtx.newXmlParser().parseResource(def.getImplementingClass(), resourceText);
}
} else if (theClient.getEncoding() == EncodingEnum.XML) {
resource = myCtx.newXmlParser().parseResource(def.getImplementingClass(), resourceText);
} else {
resource = myCtx.newJsonParser().parseResource(def.getImplementingClass(), resourceText);
}
return resource;
}
private Header[] applyHeaderFilters(Header[] theAllHeaders) {
if (myFilterHeaders == null || myFilterHeaders.isEmpty()) {
return theAllHeaders;
}
ArrayList<Header> retVal = new ArrayList<Header>();
for (Header next : theAllHeaders) {
if (!myFilterHeaders.contains(next.getName().toLowerCase())) {
retVal.add(next);
}
}
return retVal.toArray(new Header[retVal.size()]);
}
/**
* If set, the headers named here will be stripped from requests/responses before they are displayed to the user.
* This can be used, for instance, to filter out "Authorization" headers. Note that names are not case sensitive.
*/
public void setFilterHeaders(String... theHeaderNames) {
myFilterHeaders = new HashSet<String>();
if (theHeaderNames != null) {
for (String next : theHeaderNames) {
myFilterHeaders.add(next.toLowerCase());
}
}
}
private String parseNarrative(EncodingEnum theCtEnum, String theResultBody) {
try {
IResource resource = theCtEnum.newParser(myCtx).parseResource(theResultBody);
String retVal = resource.getText().getDiv().getValueAsString();
return StringUtils.defaultString(retVal);
} catch (Exception e) {
ourLog.error("Failed to parse resource", e);
return "";
}
}
private interface ConformanceClient extends IBasicClient {
@Metadata
Conformance getConformance();
}
private final class ProfileResourceResolver implements IResourceResolver {
@Override
public String getName() {
return getClass().getCanonicalName();
}
@Override
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) {
ourLog.debug("Loading template: {}", theName);
if ("/".equals(theName)) {
return RestfulServerTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/PublicTester.html");
}
if (PUBLIC_TESTER_RESULT_HTML.equals(theName)) {
return RestfulServerTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/PublicTesterResult.html");
}
return null;
}
}
}

View File

@ -1,899 +0,0 @@
package ca.uhn.fhir.rest.server.tester;
/*
* #%L
* HAPI FHIR 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%
*/
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.templateresolver.TemplateResolver;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.Conformance.Rest;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.client.GenericClient;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.gclient.IQuery;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
import ca.uhn.fhir.rest.gclient.StringParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public class RestfulTesterServlet extends HttpServlet {
private static final String PARAM_RESOURCE = "resource";
private static final String RESOURCE_COUNT_EXT_URL = "http://hl7api.sourceforge.net/hapi-fhir/res/extdefs.html#resourceCount";
private static final boolean DEBUGMODE = true;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulTesterServlet.class);
private static final String PUBLIC_TESTER_RESULT_HTML = "/PublicTesterResult.html";
private static final long serialVersionUID = 1L;
private FhirContext myCtx;
private LinkedHashMap<String, String> myIdToServerName = new LinkedHashMap<String, String>();
private LinkedHashMap<String, String> myIdToServerBase = new LinkedHashMap<String, String>();
private HashMap<String, String> myStaticResources;
private TemplateEngine myTemplateEngine;
private Set<String> myFilterHeaders;
public RestfulTesterServlet() {
myStaticResources = new HashMap<String, String>();
myStaticResources.put("jquery-2.1.0.min.js", "text/javascript");
myStaticResources.put("PublicTester.js", "text/javascript");
myStaticResources.put("PublicTester.css", "text/css");
myStaticResources.put("hapi_fhir_banner.png", "image/png");
myStaticResources.put("hapi_fhir_banner_right.png", "image/png");
myStaticResources.put("shCore.js", "text/javascript");
myStaticResources.put("shBrushJScript.js", "text/javascript");
myStaticResources.put("shBrushXml.js", "text/javascript");
myStaticResources.put("shBrushPlain.js", "text/javascript");
myStaticResources.put("shCore.css", "text/css");
myStaticResources.put("shThemeDefault.css", "text/css");
myStaticResources.put("json2.js", "text/javascript");
myStaticResources.put("minify.json.js", "text/javascript");
myStaticResources.put("css/bootstrap.min.css", "text/css");
myStaticResources.put("css/tester.css", "text/css");
myStaticResources.put("img/hapi_fhir_banner.png", "image/png");
myStaticResources.put("img/hapi_fhir_banner_right.png", "image/png");
myStaticResources.put("js/RestfulTester.js", "text/javascript");
myStaticResources.put("js/bootstrap.min.js", "text/javascript");
myStaticResources.put("js/jquery-2.1.0.min.js", "text/javascript");
myStaticResources.put("css/bootstrap-datetimepicker.min.css", "text/css");
myStaticResources.put("js/bootstrap-datetimepicker.min.js", "text/javascript");
myStaticResources.put("js/moment.min.js", "text/javascript");
myStaticResources.put("js/select2.min.js", "text/javascript");
myStaticResources.put("css/select2.css", "text/css");
myStaticResources.put("css/select2.png", "image/png");
myStaticResources.put("css/select2x2.png", "image/png");
myStaticResources.put("css/select2-spinner.gif", "image/gif");
myStaticResources.put("fonts/glyphicons-halflings-regular.eot", "application/octet-stream");
myStaticResources.put("fonts/glyphicons-halflings-regular.svg", "application/octet-stream");
myStaticResources.put("fonts/glyphicons-halflings-regular.ttf", "application/octet-stream");
myStaticResources.put("fonts/glyphicons-halflings-regular.woff", "application/octet-stream");
myStaticResources.put("fa/css/font-awesome.css", "text/css");
myStaticResources.put("fa/css/font-awesome.min.css", "text/css");
myStaticResources.put("fa/fonts/fontawesome-webfont.eot", "application/octet-stream");
myStaticResources.put("fa/fonts/fontawesome-webfont.svg", "application/octet-stream");
myStaticResources.put("fa/fonts/fontawesome-webfont.ttf", "application/octet-stream");
myStaticResources.put("fa/fonts/fontawesome-webfont.woff", "application/octet-stream");
myStaticResources.put("fa/fonts/FontAwesome.otf", "application/octet-stream");
myStaticResources.put("fa/less/bordered-pulled.less", "text/css");
myStaticResources.put("fa/less/core.less", "text/css");
myStaticResources.put("fa/less/fixed-width.less", "text/css");
myStaticResources.put("fa/less/font-awesome.less", "text/css");
myStaticResources.put("fa/less/icons.less", "text/css");
myStaticResources.put("fa/less/larger.less", "text/css");
myStaticResources.put("fa/less/list.less", "text/css");
myStaticResources.put("fa/less/mixins.less", "text/css");
myStaticResources.put("fa/less/path.less", "text/css");
myStaticResources.put("fa/less/rotated-flipped.less", "text/css");
myStaticResources.put("fa/less/spinning.less", "text/css");
myStaticResources.put("fa/less/stacked.less", "text/css");
myStaticResources.put("fa/less/variables.less", "text/css");
myStaticResources.put("fa/scss/_bordered-pulled.scss", "text/css");
myStaticResources.put("fa/scss/_core.scss", "text/css");
myStaticResources.put("fa/scss/_fixed-width.scss", "text/css");
myStaticResources.put("fa/scss/_icons.scss", "text/css");
myStaticResources.put("fa/scss/_larger.scss", "text/css");
myStaticResources.put("fa/scss/_list.scss", "text/css");
myStaticResources.put("fa/scss/_mixins.scss", "text/css");
myStaticResources.put("fa/scss/_path.scss", "text/css");
myStaticResources.put("fa/scss/_rotated-flipped.scss", "text/css");
myStaticResources.put("fa/scss/_spinning.scss", "text/css");
myStaticResources.put("fa/scss/_stacked.scss", "text/css");
myStaticResources.put("fa/scss/_variables.scss", "text/css");
myStaticResources.put("fa/scss/font-awesome.scss", "text/css");
myCtx = new FhirContext();
}
public FhirContext getFhirContext() {
return myCtx;
}
@Override
public void init(ServletConfig theConfig) throws ServletException {
myTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new ProfileResourceResolver());
myTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
myTemplateEngine.setDialect(dialect);
myTemplateEngine.initialize();
}
public void addServerBase(String theId, String theDisplayName, String theServerBase) {
Validate.notBlank(theId, "theId can not be blank");
Validate.notBlank(theDisplayName, "theDisplayName can not be blank");
Validate.notBlank(theServerBase, "theServerBase can not be blank");
myIdToServerBase.put(theId, theServerBase);
myIdToServerName.put(theId, theDisplayName);
}
private RuntimeResourceDefinition getResourceType(HttpServletRequest theReq) throws ServletException {
String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE));
RuntimeResourceDefinition def = myCtx.getResourceDefinition(resourceName);
if (def == null) {
throw new ServletException("Invalid resourceName: " + resourceName);
}
return def;
}
private void streamResponse(String theResourceName, String theContentType, HttpServletResponse theResp) throws IOException {
InputStream res = RestfulTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/" + theResourceName);
theResp.setContentType(theContentType);
IOUtils.copy(res, theResp.getOutputStream());
}
private enum ResultType {
RESOURCE, BUNDLE, TAGLIST, NONE
}
private void processAction(HttpServletRequest theReq, WebContext theContext, IGenericClient theClient, String theServerBase) {
GenericClient client = (GenericClient) theClient;
client.setKeepResponses(true);
ResultType returnsResource;
long latency = 0;
String outcomeDescription = null;
try {
String method = theReq.getParameter("action");
String prettyParam = theReq.getParameter("pretty");
if ("true".equals(prettyParam)) {
client.setPrettyPrint(true);
} else if ("false".equals(prettyParam)) {
client.setPrettyPrint(false);
}
EncodingEnum encoding = getRequestEncoding(theReq);
client.setEncoding(encoding);
long start = System.currentTimeMillis();
if ("home".equals(method)) {
return;
} else if ("conformance".equals(method)) {
returnsResource = ResultType.RESOURCE;
client.conformance();
} else if ("read".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("id"));
if (StringUtils.isBlank(id)) {
theContext.getVariables().put("errorMsg", "No ID specified");
return;
}
returnsResource = ResultType.RESOURCE;
String versionId = StringUtils.defaultString(theReq.getParameter("vid"));
if (StringUtils.isBlank(versionId)) {
versionId = null;
outcomeDescription = "Read Resource";
} else {
outcomeDescription = "VRead Resource";
}
client.read(def.getImplementingClass(), new IdDt(def.getName(), id, versionId));
} else if ("get-tags".equals(method)) {
Class<? extends IResource> resType = null;
if (isNotBlank(theReq.getParameter(PARAM_RESOURCE))) {
RuntimeResourceDefinition def = getResourceType(theReq);
resType = def.getImplementingClass();
String id = theReq.getParameter("resource-tags-id");
if (isNotBlank(id)) {
String vid = theReq.getParameter("resource-tags-vid");
if (isNotBlank(vid)) {
client.getTags().forResource(resType, id, vid).execute();
} else {
client.getTags().forResource(resType, id).execute();
}
} else {
client.getTags().forResource(resType).execute();
}
} else {
client.getTags().execute();
}
returnsResource = ResultType.TAGLIST;
outcomeDescription = "Tag List";
} else if ("page".equals(method)) {
String url = defaultString(theReq.getParameter("page-url"));
if (!url.startsWith(theServerBase)) {
theContext.getVariables().put("errorMsg", "Invalid page URL: " + url);
return;
}
url = url.replace("&amp;", "&");
client.loadPage().url(url).execute();
returnsResource = ResultType.BUNDLE;
outcomeDescription = "Bundle Page";
} else if ("delete".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
String id = StringUtils.defaultString(theReq.getParameter("resource-delete-id"));
if (StringUtils.isBlank(id)) {
theContext.getVariables().put("errorMsg", "No ID specified");
return;
}
returnsResource = ResultType.BUNDLE;
outcomeDescription = "Delete Resource";
client.delete(def.getImplementingClass(), new IdDt(id));
} else if ("history-resource".equals(method) || "history-server".equals(method)) {
String id = null;
Class<? extends IResource> type = null; // def.getImplementingClass();
if (!"history-server".equals(method)) {
RuntimeResourceDefinition def = getResourceType(theReq);
type = def.getImplementingClass();
id = StringUtils.defaultString(theReq.getParameter("resource-history-id"));
if (StringUtils.isBlank(id)) {
if ("history-instance".equals(method)) {
theContext.getVariables().put("errorMsg", "No ID specified");
return;
} else {
id = null;
}
}
}
DateTimeDt since = null;
String sinceStr = theReq.getParameter("since");
if (isNotBlank(sinceStr)) {
since = new DateTimeDt(sinceStr);
}
Integer limit = null;
String limitStr = theReq.getParameter("limit");
if (isNotBlank(limitStr)) {
limit = Integer.parseInt(limitStr);
}
returnsResource = ResultType.BUNDLE;
outcomeDescription = "Resource History";
client.history(type, id, since, limit);
} else if ("create".equals(method) || "validate".equals(method)) {
boolean validate = "validate".equals(method);
String body = validate ? theReq.getParameter("resource-validate-body") : theReq.getParameter("resource-create-body");
if (isBlank(body)) {
theContext.getVariables().put("errorMsg", "No message body specified");
return;
}
body = body.trim();
IResource resource;
try {
if (body.startsWith("{")) {
resource = myCtx.newJsonParser().parseResource(body);
} else if (body.startsWith("<")) {
resource = myCtx.newXmlParser().parseResource(body);
} else {
theContext.getVariables().put("errorMsg",
"Message body does not appear to be a valid FHIR resource instance document. Body should start with '<' (for XML encoding) or '{' (for JSON encoding).");
return;
}
} catch (DataFormatException e) {
ourLog.warn("Failed to parse resource", e);
theContext.getVariables().put("errorMsg", "Failed to parse message body. Error was: " + e.getMessage());
return;
}
if (validate) {
client.validate(resource);
outcomeDescription = "Validate Resource";
} else {
String id = theReq.getParameter("resource-create-id");
if (isNotBlank(id)) {
outcomeDescription = "Update Resource";
client.update(id, resource);
} else {
outcomeDescription = "Create Resource";
client.create(resource);
}
}
returnsResource = ResultType.RESOURCE;
} else if ("search".equals(method)) {
IUntypedQuery search = client.search();
IQuery query;
if (isNotBlank(theReq.getParameter("resource"))) {
query = search.forResource(getResourceType(theReq).getImplementingClass());
} else {
query = search.forAllResources();
}
outcomeDescription = "Search for Resources";
int paramIdx = -1;
while (true) {
paramIdx++;
String paramIdxString = Integer.toString(paramIdx);
boolean shouldContinue = handleSearchParam(paramIdxString, theReq, query);
if (!shouldContinue) {
break;
}
}
String[] incValues = theReq.getParameterValues(Constants.PARAM_INCLUDE);
if (incValues != null) {
for (String next : incValues) {
if (isNotBlank(next)) {
query.include(new Include(next));
}
}
}
String limit = theReq.getParameter("resource-search-limit");
if (isNotBlank(limit)) {
if (!limit.matches("[0-9]+")) {
theContext.getVariables().put("errorMsg", "Search limit must be a numeric value.");
return;
}
query.limitTo(Integer.parseInt(limit));
}
query.execute();
returnsResource = ResultType.BUNDLE;
} else {
theContext.getVariables().put("errorMsg", "Invalid action: " + method);
return;
}
latency = System.currentTimeMillis() - start;
} catch (DataFormatException e) {
ourLog.error("Failed to invoke method", e);
returnsResource = ResultType.NONE;
} catch (BaseServerResponseException e) {
ourLog.error("Failed to invoke method", e);
returnsResource = ResultType.NONE;
} catch (Exception e) {
ourLog.error("Failure during processing", e);
returnsResource = ResultType.NONE;
}
try {
HttpRequestBase lastRequest = client.getLastRequest();
String requestBody = null;
String requestUrl = lastRequest != null ? lastRequest.getURI().toASCIIString() : null;
String action = client.getLastRequest() != null ? client.getLastRequest().getMethod() : null;
String resultStatus = client.getLastResponse() != null ? client.getLastResponse().getStatusLine().toString() : null;
String resultBody = client.getLastResponseBody();
if (lastRequest instanceof HttpEntityEnclosingRequest) {
HttpEntity entity = ((HttpEntityEnclosingRequest) lastRequest).getEntity();
if (entity.isRepeatable()) {
requestBody = IOUtils.toString(entity.getContent());
}
}
HttpResponse lastResponse = client.getLastResponse();
ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null;
String mimeType = ct != null ? ct.getMimeType() : null;
EncodingEnum ctEnum = EncodingEnum.forContentType(mimeType);
String narrativeString = "";
StringBuilder resultDescription = new StringBuilder();
Bundle bundle = null;
if (ctEnum == null) {
resultDescription.append("Non-FHIR response");
} else {
switch (ctEnum) {
case JSON:
if (returnsResource == ResultType.RESOURCE) {
narrativeString = parseNarrative(ctEnum, resultBody);
resultDescription.append("JSON resource");
} else if (returnsResource == ResultType.BUNDLE) {
resultDescription.append("JSON bundle");
bundle = myCtx.newJsonParser().parseBundle(resultBody);
}
break;
case XML:
default:
if (returnsResource == ResultType.RESOURCE) {
narrativeString = parseNarrative(ctEnum, resultBody);
resultDescription.append("XML resource");
} else if (returnsResource == ResultType.BUNDLE) {
resultDescription.append("XML bundle");
bundle = myCtx.newXmlParser().parseBundle(resultBody);
}
break;
}
}
resultDescription.append(" (").append(resultBody.length() + " bytes)");
Header[] requestHeaders = lastRequest != null ? applyHeaderFilters(lastRequest.getAllHeaders()) : new Header[0];
Header[] responseHeaders = lastResponse != null ? applyHeaderFilters(lastResponse.getAllHeaders()) : new Header[0];
theContext.setVariable("outcomeDescription", outcomeDescription);
theContext.setVariable("resultDescription", resultDescription.toString());
theContext.setVariable("action", action);
theContext.setVariable("bundle", bundle);
theContext.setVariable("resultStatus", resultStatus);
theContext.setVariable("requestUrl", requestUrl);
String requestBodyText = format(requestBody, ctEnum);
theContext.setVariable("requestBody", requestBodyText);
String resultBodyText = format(resultBody, ctEnum);
theContext.setVariable("resultBody", resultBodyText);
theContext.setVariable("resultBodyIsLong", resultBodyText.length() > 1000);
theContext.setVariable("requestHeaders", requestHeaders);
theContext.setVariable("responseHeaders", responseHeaders);
theContext.setVariable("narrative", narrativeString);
theContext.setVariable("latencyMs", latency);
} catch (Exception e) {
ourLog.error("Failure during processing", e);
theContext.getVariables().put("errorMsg", "Error during processing: " + e.getMessage());
}
}
private boolean handleSearchParam(String paramIdxString, HttpServletRequest theReq, IQuery theQuery) {
String nextName = theReq.getParameter("param." + paramIdxString + ".name");
if (isBlank(nextName)) {
return false;
}
String nextQualifier = StringUtils.defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier"));
String nextType = theReq.getParameter("param." + paramIdxString + ".type");
StringBuilder b = new StringBuilder();
for (int i = 0; i < 100; i++) {
b.append(defaultString(theReq.getParameter("param." + paramIdxString + "." + i)));
}
String paramValue = b.toString();
if (isBlank(paramValue)) {
return true;
}
if ("token".equals(nextType)) {
if (paramValue.length() < 2) {
return true;
}
}
// if ("xml".equals(theReq.getParameter("encoding"))) {
// query.encodedXml();
// }else if ("json".equals(theReq.getParameter("encoding"))) {
// query.encodedJson();
// }
theQuery.where(new StringParam(nextName + nextQualifier).matches().value(paramValue));
if (StringUtils.isNotBlank(theReq.getParameter("param." + paramIdxString + ".0.name"))) {
handleSearchParam(paramIdxString + ".0", theReq, theQuery);
}
return true;
}
private String format(String theResultBody, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
return str;
}
StringBuilder b = new StringBuilder();
if (theEncodingEnum == EncodingEnum.JSON) {
boolean inValue = false;
boolean inQuote = false;
for (int i = 0; i < str.length(); i++) {
char prevChar = (i > 0) ? str.charAt(i - 1) : ' ';
char nextChar = str.charAt(i);
char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' ';
char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' ';
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (inQuote) {
b.append(nextChar);
if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;</span>");
i += 5;
inQuote = false;
} else if (nextChar == '\\' && nextChar2 == '"') {
b.append("quot;</span>");
i += 5;
inQuote = false;
}
} else {
if (nextChar == ':') {
inValue = true;
b.append(nextChar);
} else if (nextChar == '[' || nextChar == '{') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
inValue = false;
} else if (nextChar == '}' || nextChar == '}' || nextChar == ',') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
inValue = false;
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
if (inValue) {
b.append("<span class='hlQuot'>&quot;");
} else {
b.append("<span class='hlTagName'>&quot;");
}
inQuote = true;
i += 5;
} else if (nextChar == ':') {
b.append("<span class='hlControl'>");
b.append(nextChar);
b.append("</span>");
inValue = true;
} else {
b.append(nextChar);
}
}
}
} else {
boolean inQuote = false;
boolean inTag = false;
for (int i = 0; i < str.length(); i++) {
char nextChar = str.charAt(i);
char nextChar2 = (i + 1) < str.length() ? str.charAt(i + 1) : ' ';
char nextChar3 = (i + 2) < str.length() ? str.charAt(i + 2) : ' ';
char nextChar4 = (i + 3) < str.length() ? str.charAt(i + 3) : ' ';
char nextChar5 = (i + 4) < str.length() ? str.charAt(i + 4) : ' ';
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (inQuote) {
b.append(nextChar);
if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("quot;</span>");
i += 5;
inQuote = false;
}
} else if (inTag) {
if (nextChar == '&' && nextChar2 == 'g' && nextChar3 == 't' && nextChar4 == ';') {
b.append("</span><span class='hlControl'>&gt;</span>");
inTag = false;
i += 3;
} else if (nextChar == ' ') {
b.append("</span><span class='hlAttr'>");
b.append(nextChar);
} else if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
b.append("<span class='hlQuot'>&quot;");
inQuote = true;
i += 5;
} else {
b.append(nextChar);
}
} else {
if (nextChar == '&' && nextChar2 == 'l' && nextChar3 == 't' && nextChar4 == ';') {
b.append("<span class='hlControl'>&lt;</span><span class='hlTagName'>");
inTag = true;
i += 3;
} else {
b.append(nextChar);
}
}
}
}
return b.toString();
}
private EncodingEnum getRequestEncoding(HttpServletRequest theReq) {
EncodingEnum encoding;
if ("xml".equals(theReq.getParameter("encoding"))) {
encoding = EncodingEnum.XML;
} else if ("json".equals(theReq.getParameter("encoding"))) {
encoding = (EncodingEnum.JSON);
} else {
encoding = null;
}
return encoding;
}
@Override
protected void doGet(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
if (DEBUGMODE) {
myTemplateEngine.getCacheManager().clearAllCaches();
}
try {
ourLog.trace("RequestURI: {}", theReq.getPathInfo());
String resName = theReq.getPathInfo().substring(1);
if (myStaticResources.containsKey(resName)) {
streamResponse(resName, myStaticResources.get(resName), theResp);
return;
}
String serverId = theReq.getParameter("server-id");
String serverBase;
String serverName;
if (isBlank(serverId) && !myIdToServerBase.containsKey(serverId)) {
serverBase = myIdToServerBase.entrySet().iterator().next().getValue();
serverName = myIdToServerName.entrySet().iterator().next().getValue();
} else {
serverBase = myIdToServerBase.get(serverId);
serverName = myIdToServerName.get(serverId);
}
IGenericClient client = myCtx.newRestfulGenericClient(serverBase);
WebContext ctx = new WebContext(theReq, theResp, theReq.getServletContext(), theReq.getLocale());
Conformance conformance;
try {
conformance = client.conformance();
} catch (Exception e) {
ourLog.warn("Failed to load conformance statement", e);
ctx.getVariables().put("errorMsg", "Failed to load conformance statement, error was: " + e.toString());
conformance = new Conformance();
}
Map<String, Number> resourceCounts = new HashMap<String, Number>();
long total = 0;
for (Rest nextRest : conformance.getRest()) {
for (RestResource nextResource : nextRest.getResource()) {
List<ExtensionDt> exts = nextResource.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL);
if (exts != null && exts.size() > 0) {
Number nextCount = ((DecimalDt) (exts.get(0).getValue())).getValueAsNumber();
resourceCounts.put(nextResource.getType().getValue(), nextCount);
total += nextCount.longValue();
}
}
}
if (total > 0) {
for (Rest nextRest : conformance.getRest()) {
Collections.sort(nextRest.getResource(), new Comparator<RestResource>() {
@Override
public int compare(RestResource theO1, RestResource theO2) {
DecimalDt count1 = new DecimalDt();
List<ExtensionDt> count1exts = theO1.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL);
if (count1exts != null && count1exts.size() > 0) {
count1 = (DecimalDt) count1exts.get(0).getValue();
}
DecimalDt count2 = new DecimalDt();
List<ExtensionDt> count2exts = theO2.getUndeclaredExtensionsByUrl(RESOURCE_COUNT_EXT_URL);
if (count2exts != null && count2exts.size() > 0) {
count2 = (DecimalDt) count2exts.get(0).getValue();
}
int retVal = count2.compareTo(count1);
if (retVal == 0) {
retVal = theO1.getType().getValue().compareTo(theO2.getType().getValue());
}
return retVal;
}
});
}
}
ctx.setVariable("serverId", serverId);
ctx.setVariable("resourceCounts", resourceCounts);
ctx.setVariable("conf", conformance);
ctx.setVariable("base", serverBase);
ctx.setVariable("baseName", serverName);
ctx.setVariable("serverEntries", myIdToServerName.entrySet());
String resourceName = defaultString(theReq.getParameter(PARAM_RESOURCE));
ctx.setVariable("resourceName", resourceName);
ctx.setVariable("jsonEncodedConf", myCtx.newJsonParser().encodeResourceToString(conformance));
addStandardVariables(ctx, theReq.getParameterMap());
if (isNotBlank(theReq.getParameter("action"))) {
processAction(theReq, ctx, client, serverBase);
}
if (isNotBlank(resourceName)) {
RuntimeResourceDefinition def = myCtx.getResourceDefinition(resourceName);
TreeSet<String> includes = new TreeSet<String>();
for (Rest nextRest : conformance.getRest()) {
for (RestResource nextRes : nextRest.getResource()) {
if (nextRes.getType().getValue().equals(resourceName)) {
for (StringDt next : nextRes.getSearchInclude()) {
if (next.isEmpty() == false) {
includes.add(next.getValue());
}
}
}
}
}
ctx.setVariable("includes", includes);
if (isNotBlank(theReq.getParameter("update-id"))) {
String updateId = theReq.getParameter("update-id");
String updateVid = defaultIfEmpty(theReq.getParameter("update-vid"), null);
IResource updateResource = client.read(def.getImplementingClass(), new IdDt(resourceName, updateId, updateVid));
EncodingEnum encoding = getRequestEncoding(theReq);
if (encoding == null) {
encoding = EncodingEnum.XML;
}
String updateResourceString = encoding.newParser(myCtx).setPrettyPrint(true).encodeResourceToString(updateResource);
ctx.setVariable("updateResource", updateResourceString);
ctx.setVariable("updateResourceId", updateId);
}
}
theResp.setContentType("text/html");
theResp.setCharacterEncoding("UTF-8");
myTemplateEngine.process(theReq.getPathInfo(), ctx, theResp.getWriter());
} catch (Exception e) {
ourLog.error("Failed to respond", e);
theResp.sendError(500, e.getMessage());
}
}
private void addStandardVariables(WebContext theCtx, Map<String, String[]> theParameterMap) {
addStandardVariable(theCtx, theParameterMap, "encoding");
addStandardVariable(theCtx, theParameterMap, "pretty");
}
private void addStandardVariable(WebContext theCtx, Map<String, String[]> theParameterMap, String key) {
if (theParameterMap.containsKey(key) && theParameterMap.get(key).length > 0) {
theCtx.setVariable(key, theParameterMap.get(key)[0]);
}
}
private Header[] applyHeaderFilters(Header[] theAllHeaders) {
if (myFilterHeaders == null || myFilterHeaders.isEmpty()) {
return theAllHeaders;
}
ArrayList<Header> retVal = new ArrayList<Header>();
for (Header next : theAllHeaders) {
if (!myFilterHeaders.contains(next.getName().toLowerCase())) {
retVal.add(next);
}
}
return retVal.toArray(new Header[retVal.size()]);
}
/**
* If set, the headers named here will be stripped from requests/responses before they are displayed to the user. This can be used, for instance, to filter out "Authorization" headers. Note that
* names are not case sensitive.
*/
public void setFilterHeaders(String... theHeaderNames) {
myFilterHeaders = new HashSet<String>();
if (theHeaderNames != null) {
for (String next : theHeaderNames) {
myFilterHeaders.add(next.toLowerCase());
}
}
}
private String parseNarrative(EncodingEnum theCtEnum, String theResultBody) {
try {
IResource resource = theCtEnum.newParser(myCtx).parseResource(theResultBody);
String retVal = resource.getText().getDiv().getValueAsString();
return StringUtils.defaultString(retVal);
} catch (Exception e) {
ourLog.error("Failed to parse resource", e);
return "";
}
}
private final class ProfileResourceResolver implements IResourceResolver {
@Override
public String getName() {
return getClass().getCanonicalName();
}
@Override
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) {
ourLog.debug("Loading template: {}", theName);
if ("/".equals(theName)) {
return RestfulTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/RestfulTester.html");
}
if (PUBLIC_TESTER_RESULT_HTML.equals(theName)) {
return RestfulTesterServlet.class.getResourceAsStream("/ca/uhn/fhir/rest/server/tester/PublicTesterResult.html");
}
return null;
}
}
}

View File

@ -1,60 +0,0 @@
A.selectedFunctionLink {
text-decoration: none;
background: #E0FFE0;
border: 1px #80C080 solid;
color: #005000;
}
BODY {
font-family: sans-serif;
}
SPAN.headerName {
color: #505080;
}
SPAN.headerValue {
color: #70A070;
}
DIV.bodyHeaderBlock {
background-color: #E0E0E0;
margin-top: 5px;
border-radius: 5px;
padding: 5px;
}
DIV.textareaWrapper {
position:relative;
height:100%;
width:100%;
}
DIV.textareaWrapper TEXTAREA {
width:95%;
overflow: scroll;
}
TABLE.propertyTable {
margin-left: 4px;
width: 95%;
}
TD.propertyKeyCell {
background-color: #E0E0FF;
border-radius: 3px;
padding: 3px;
width: 100px;
}
TD.testerNameCell {
background-color: #E0FFE0;
border-radius: 3px;
padding: 3px;
}
.syntaxhighlighter {
font-size: 0.85em !important;
min-height: 1.4em;
max-width: 800px;
}

View File

@ -1,159 +0,0 @@
<!DOCTYPE html>
<!--/*
************************************************************
This file is a Thymeleaf template for the
************************************************************
*/-->
<html>
<head>
<script type="text/javascript">
var serverBase = "<th:block th:text="${base}"/>";
var conformance = <th:block th:utext="${jsonEncodedConf}"/>;
</script>
<script type="text/javascript" src="jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="PublicTester.js"></script>
<link rel="stylesheet" type="text/css" href="PublicTester.css"/>
<meta charset="UTF-8"/>
<script type="text/javascript" src="json2.js"></script>
<script type="text/javascript" src="minify.json.js"></script>
</head>
<body onload="selectResourceType();">
<table border="0" width="100%">
<tr>
<td align="left"><img src="hapi_fhir_banner.png"/></td>
<td align="right"><img src="hapi_fhir_banner_right.png"/></td>
</tr>
</table>
<div class="bodyHeaderBlock">
This is a RESTful server tester, which can be used to send requests to, and receive responses
from the server at the following URL:<br/>
<a href="http://foo.com/fhir" th:href="${base}"><th:block th:text="${base}">http://foo.com/fhir</th:block></a>
</div>
<table border="0" width="100%" cellpadding="0" cellspacing="0" style="margin-top: 4px;">
<tr>
<td width="29%" valign="top">
<div class="bodyHeaderBlock">
Software Details
</div>
<table border="0" class="propertyTable">
<tr>
<td class="propertyKeyCell">Software</td>
<td th:text="${conf.software.name}">HAPI Restful Server</td>
</tr>
<tr>
<td class="propertyKeyCell">Version</td>
<td th:text="${conf.software.version}">1.1.1</td>
</tr>
</table>
<div class="bodyHeaderBlock">
Request Configuration
</div>
<table border="0" class="propertyTable">
<tr>
<td class="propertyKeyCell">Encoding</td>
<td>
<select id="configEncoding" th:switch="${configEncoding}">
<th:block th:case="'xml'">
<option value="">(default)</option>
<option value="xml" selected="selected">XML</option>
<option value="json">JSON</option>
</th:block>
<th:block th:case="'json'">
<option value="">(default)</option>
<option value="xml">XML</option>
<option value="json" selected="selected">JSON</option>
</th:block>
<th:block th:case="*">
<option value="" selected="selected">(default)</option>
<option value="xml">XML</option>
<option value="json">JSON</option>
</th:block>
</select>
</td>
</tr>
<tr>
<td class="propertyKeyCell">Pretty Printing</td>
<td th:switch="${configPretty}">
<input th:case="'false'" type="checkbox" id="configPretty"/>
<input th:case="*" type="checkbox" id="configPretty" checked="checked"/>
</td>
</tr>
</table>
</td>
<td width="1%"/>
<td width="70%" valign="top">
<th:block th:each="rest : ${conf.rest}">
<div class="bodyHeaderBlock">
RESTful Server
</div>
<table border="0" th:id="systemExpando" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Supports operations:</td>
<td valign="top">
<a th:onclick="'displayConformance(this, \'systemExpando\'); return false;'" href="#">conformance</a>
<th:span th:each="operation : ${rest.operation}">
<th:block th:switch="${operation.code.value}">
<span th:case="*" th:text="${operation.code.value}"/>
</th:block>
</th:span>
</td>
</tr>
</table>
<div class="bodyHeaderBlock">
Resources
</div>
<table border="0" th:id="systemExpando" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Select Resource:</td>
<td valign="top">
<select id="configResource" onchange="selectResourceType();">
<th:block th:each="resource, resIterStat : ${rest.resource}">
<option th:value="${resource.type.valueAsString}" th:text="${resource.type.valueAsString}"/>
</th:block>
</select>
</td>
</tr>
</table>
<div th:each="resource, resIterStat : ${rest.resource}" th:with="expandoId='resExpando'+${resIterStat.count}" th:id="'res' + ${resource.type.valueAsString}" class="resourceTypeContainer" style="display:none;">
<div class="bodyHeaderBlock">
Resource: <th:block th:text="${resource.type.valueAsString}"/>
</div>
<table border="0" th:id="${expandoId}" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Supports operations:</td>
<td valign="top">
<th:span th:each="operation : ${resource.operation}">
<th:block th:switch="${operation.code.value}">
<a th:case="'read'" href="#" th:onclick="'displayRead(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">read</a>
<a th:case="'vread'" href="#" th:onclick="'displayVRead(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">vread</a>
<a th:case="'search-type'" href="#" th:onclick="'displaySearchType(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">search-type</a>
<a th:case="'create'" href="#" th:onclick="'displayCreate(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">create</a>
<a th:case="'update'" href="#" th:onclick="'displayUpdate(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">update</a>
<a th:case="'validate'" href="#" th:onclick="'displayValidate(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">validate</a>
<a th:case="'delete'" href="#" th:onclick="'displayDelete(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">delete</a>
<a th:case="'history-instance'" href="#" th:onclick="'displayHistoryInstance(this, \'' + ${expandoId} + '\', \'' + ${resource.type.valueAsString} + '\'); return false;'">history-instance</a>
<span th:case="*" href="#" th:text="${operation.code.value}"/>
</th:block>
</th:span>
</td>
</tr>
</table>
</div>
</th:block>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,360 +0,0 @@
var uniqueIdSeed = 1;
function addConfigElementsToForm(theForm) {
var encoding = document.getElementById('configEncoding');
var pretty = document.getElementById('configPretty');
var newEncoding = document.createElement("input");
newEncoding.type='hidden';
newEncoding.name='configEncoding';
newEncoding.value = encoding.value;
theForm.appendChild(newEncoding);
var newPretty = document.createElement("input");
newPretty.type='hidden';
newPretty.name='configPretty';
if (pretty.checked) {
newPretty.value='on';
}
newPretty.checked = pretty.checked;
theForm.appendChild(newPretty);
}
function appendSearchParam(formElement, searchParam) {
var inputId = newUniqueId();
if (searchParam.type && searchParam.type == 'token') {
formElement.append(
$('<input />', { name: 'param.token.1.' + searchParam.name, placeholder: 'system/namespace', type: 'text', id: inputId }),
$('<input />', { name: 'param.token.2.' + searchParam.name, placeholder: 'value', type: 'text', id: inputId }),
$('<label for="' + inputId + '">' + searchParam.name + '</input>')
);
} else {
formElement.append(
$('<input />', { name: 'param.string.' + searchParam.name, placeholder: searchParam.name, type: 'text', id: inputId }),
$('<label for="' + inputId + '">' + searchParam.name + '</input>')
);
}
}
function appendSearchParamExtension(extension) {
}
/** Hide any currently displayed tester form */
function clearCurrentForm(postCompleteFunction) {
/* $('.testerNameRow').each().fadeOut(500).promise().then(function(){
if (postCompleteFunction != null) {
$('.testerNameRow').each().remove();
postCompleteFunction();
postCompleteFunction = null;
}
}); */
var current = $('.testerNameRow');
if (current.length == 0) {
postCompleteFunction();
} else {
current.first().fadeOut(300, function() {
current.first().remove();
clearCurrentForm(postCompleteFunction);
});
}
}
/** Create a tester form for the 'read' method */
function displayConformance(button, expandoTr) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Conformance</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'conformance', type: 'hidden' }),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
function minifyTextarea(self) {
var value = value;
value = value.replace(/[\u00A0\u1680\u180e\u2000-\u2009\u200a\u200b\u202f\u205f\u3000]/g,' ');
//value = JSON.minify(value);
$('#textarea').val(value);
return addConfigElementsToForm(self);
}
/** Create a tester form for the 'read' method */
function displayCreate(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Create</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "minifyTextarea(this);" }).append(
$('<input />', { name: 'method', value: 'create', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<div class="textareaWrapper">').append(
$('<textarea />', { id: 'textarea', name: 'resource', rows: 10, style: 'white-space: nowrap;' })
),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'delete' method */
function displayDelete(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Delete</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'delete', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayUpdate(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Update</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "minifyTextarea(this);" }).append(
$('<input />', { name: 'method', value: 'update', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<textarea />', { id: 'textarea', name: 'resource', cols: 100, rows: 10, style: 'white-space: nowrap;' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'search' method */
function displaySearchType(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
// Add a search form for the default (no parameter) search, which should
// return all resources of the given type
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Search by Type</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'searchType', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<span>All Resources of Type</span><br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
// Loop through each supported search parameter and add a search form for it
conformance.rest.forEach(function(rest){
rest.resource.forEach(function(restResource){
if (restResource.type == resourceName) {
if (restResource.searchParam) {
var paramIndex = 0;
restResource.searchParam.forEach(function(searchParam){
var formElement = $('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" });
formElement.append(
$('<input />', { name: 'method', value: 'searchType', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' })
)
if (searchParam.documentation && searchParam.documentation.length > 0) {
formElement.append(
$('<span>' + searchParam.documentation + '<br /></span>')
);
}
appendSearchParam(formElement, searchParam);
// http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParam
if (restResource._searchParam && restResource._searchParam[paramIndex] != null) {
if (restResource._searchParam[paramIndex].extension) {
appendSearchParamExtension(restResource._searchParam[paramIndex].extension);
}
}
formElement.append(
$('<br />')
);
formElement.append(
$('<input />', { type: 'submit', value: 'Submit' })
);
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Search by Type</td>'),
$('<td />').append(
formElement
)
)
);
paramIndex++;
});
}
}
});
});
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'instance-history' method */
function displayHistoryInstance(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Read</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'history-instance', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayRead(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Read</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'read', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayValidate(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Validate</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "minifyTextarea(this);" }).append(
$('<input />', { name: 'method', value: 'validate', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<textarea />', { id: 'textarea', name: 'resource', cols: 100, rows: 10, style: 'white-space: nowrap;' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
/** Create a tester form for the 'read' method */
function displayVRead(button, expandoTr, resourceName) {
highlightSelectedLink(button);
var postCompleteFunction = function() {
$('#' + expandoTr).append(
$('<tr class="testerNameRow" style="display: none;" />').append(
$('<td class="testerNameCell">Read</td>'),
$('<td />').append(
$('<form/>', { action: 'PublicTesterResult.html', method: 'POST', onsubmit: "addConfigElementsToForm(this);" }).append(
$('<input />', { name: 'method', value: 'vread', type: 'hidden' }),
$('<input />', { name: 'resourceName', value: resourceName, type: 'hidden' }),
$('<input />', { name: 'id', placeholder: 'Resource ID', type: 'text' }),
$('<input />', { name: 'versionid', placeholder: 'Version ID', type: 'text' }),
$('<br />'),
$('<input />', { type: 'submit', value: 'Submit' })
)
)
)
);
showNewForm();
}
clearCurrentForm(postCompleteFunction);
}
function highlightSelectedLink(button) {
$('.selectedFunctionLink').each(function() {
$(this).removeClass('selectedFunctionLink');
});
button.className = 'selectedFunctionLink';
}
/** Generate a unique ID */
function newUniqueId() {
return "uid" + uniqueIdSeed++;
}
function selectResourceType() {
$('.resourceTypeContainer').each(function() {
$(this).hide();
});
var cr = document.getElementById('configResource');
var selected = cr.options[cr.selectedIndex].value;
$('#res' + selected).show();
}
/** Show a newly created tester form */
function showNewForm() {
var time = 0;
$('.testerNameRow').each(function() {
$(this).delay(time).fadeIn(200);
time += 200;
});
}

View File

@ -1,120 +0,0 @@
<!DOCTYPE html>
<!--/*
************************************************************
This file is a Thymeleaf template for the
************************************************************
*/-->
<html>
<head>
<script src="shCore.js" type="text/javascript"></script>
<script src="shBrushJScript.js" type="text/javascript"></script>
<script src="shBrushXml.js" type="text/javascript"></script>
<script src="shBrushPlain.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shCore.css"/>
<link rel="stylesheet" type="text/css" href="shThemeDefault.css"/>
<script type="text/javascript" src="jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="PublicTester.js"></script>
<script type="text/javascript">
var serverBase = "<th:block th:utext="${base}"/>";
</script>
<link rel="stylesheet" type="text/css" href="PublicTester.css"/>
</head>
<body>
<table border="0" width="100%">
<tr>
<td align="left"><img src="hapi_fhir_banner.png"/></td>
<td align="right"><img src="hapi_fhir_banner_right.png"/></td>
</tr>
</table>
<div class="bodyHeaderBlock">
Executed invocation against FHIR RESTful Server in
<th:block th:text="${latencyMs} + 'ms'"/>
</div>
<table border="0" width="100%" cellpadding="0" cellspacing="0" style="margin-top: 4px;">
<tr>
<td width="29%" valign="top">
<div class="bodyHeaderBlock">
Actions
</div>
<table border="0">
<tr>
<td class="propertyKeyCell">Something</td>
<td>Something</td>
</tr>
</table>
</td>
<td width="1%"/>
<td width="70%" valign="top">
<div class="bodyHeaderBlock">
Request
</div>
<table border="0" class="propertyTable">
<tr>
<td class="propertyKeyCell">Action</td>
<td th:text="${action}">GET</td>
</tr>
<tr>
<td class="propertyKeyCell">URL</td>
<td>
<a href="http://foo.com/fhir" th:href="${requestUrl}"><th:block th:text="${requestUrl}">http://foo.com/fhir</th:block></a>
</td>
</tr>
<tr > <!-- th:if="${not #lists.isEmpty(requestHeaders)}" -->
<td valign="top" class="propertyKeyCell">Headers</td>
<td valign="top">
<div th:each="header : ${requestHeaders}">
<span class="headerName" th:text="${header.name} + ': '"/>
<span class="headerValue" th:text="${header.value}"/>
</div>
</td>
</tr>
<tr th:if="${requestBody} != null">
<td valign="top" class="propertyKeyCell">Request Body</td>
<td valign="top"><pre th:text="${requestBody}" th:class="${requestSyntaxHighlighterClass}">{}</pre></td>
</tr>
</table>
<div class="bodyHeaderBlock">
Response
</div>
<table border="0" class="propertyTable">
<tr>
<td valign="top" class="propertyKeyCell">Status</td>
<td valign="top" th:text="${resultStatus}">HTTP 200 OK</td>
</tr>
<tr > <!-- th:if="${not #lists.isEmpty(responseHeaders)}" -->
<td valign="top" class="propertyKeyCell">Headers</td>
<td valign="top">
<div th:each="header : ${responseHeaders}">
<span class="headerName" th:text="${header.name} + ': '"/>
<span class="headerValue" th:text="${header.value}"/>
</div>
</td>
</tr>
<tr th:if="${!#strings.isEmpty(resultBody)}">
<td valign="top" class="propertyKeyCell">Result Body</td>
<td valign="top"><pre th:text="${resultBody}" th:class="${resultSyntaxHighlighterClass}">{}</pre></td>
</tr>
<tr th:if="${!#strings.isEmpty(narrative)}">
<td valign="top" class="propertyKeyCell">Result Narrative</td>
<td valign="top" th:utext="${narrative}"></td>
</tr>
</table>
</td>
</tr>
</table>
<script type="text/javascript">
SyntaxHighlighter.all();
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,11 +0,0 @@
/*!
http://www.JSON.org/json2.js
2009-09-29
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
*/
if(!this.JSON){this.JSON={}}(function(){function l(c){return c<10?'0'+c:c}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(c){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+l(this.getUTCMonth()+1)+'-'+l(this.getUTCDate())+'T'+l(this.getUTCHours())+':'+l(this.getUTCMinutes())+':'+l(this.getUTCSeconds())+'Z':null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(c){return this.valueOf()}}var o=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,p=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,h,m,r={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},j;function q(a){p.lastIndex=0;return p.test(a)?'"'+a.replace(p,function(c){var f=r[c];return typeof f==='string'?f:'\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function n(c,f){var a,e,d,i,k=h,g,b=f[c];if(b&&typeof b==='object'&&typeof b.toJSON==='function'){b=b.toJSON(c)}if(typeof j==='function'){b=j.call(f,c,b)}switch(typeof b){case'string':return q(b);case'number':return isFinite(b)?String(b):'null';case'boolean':case'null':return String(b);case'object':if(!b){return'null'}h+=m;g=[];if(Object.prototype.toString.apply(b)==='[object Array]'){i=b.length;for(a=0;a<i;a+=1){g[a]=n(a,b)||'null'}d=g.length===0?'[]':h?'[\n'+h+g.join(',\n'+h)+'\n'+k+']':'['+g.join(',')+']';h=k;return d}if(j&&typeof j==='object'){i=j.length;for(a=0;a<i;a+=1){e=j[a];if(typeof e==='string'){d=n(e,b);if(d){g.push(q(e)+(h?': ':':')+d)}}}}else{for(e in b){if(Object.hasOwnProperty.call(b,e)){d=n(e,b);if(d){g.push(q(e)+(h?': ':':')+d)}}}}d=g.length===0?'{}':h?'{\n'+h+g.join(',\n'+h)+'\n'+k+'}':'{'+g.join(',')+'}';h=k;return d}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(c,f,a){var e;h='';m='';if(typeof a==='number'){for(e=0;e<a;e+=1){m+=' '}}else if(typeof a==='string'){m=a}j=f;if(f&&typeof f!=='function'&&(typeof f!=='object'||typeof f.length!=='number')){throw new Error('JSON.stringify');}return n('',{'':c})}}if(typeof JSON.parse!=='function'){JSON.parse=function(i,k){var g;function b(c,f){var a,e,d=c[f];if(d&&typeof d==='object'){for(a in d){if(Object.hasOwnProperty.call(d,a)){e=b(d,a);if(e!==undefined){d[a]=e}else{delete d[a]}}}}return k.call(c,f,d)}o.lastIndex=0;if(o.test(i)){i=i.replace(o,function(c){return'\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(i.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){g=eval('('+i+')');return typeof k==='function'?b({'':g},''):g}throw new SyntaxError('JSON.parse');}}}());

View File

@ -1,61 +0,0 @@
/*! JSON.minify()
v0.1 (c) Kyle Simpson
MIT License
*/
(function(global){
if (typeof global.JSON == "undefined" || !global.JSON) {
global.JSON = {};
}
global.JSON.minify = function(json) {
var tokenizer = /"|(\/\*)|(\*\/)|(\/\/)|\n|\r/g,
in_string = false,
in_multiline_comment = false,
in_singleline_comment = false,
tmp, tmp2, new_str = [], ns = 0, from = 0, lc, rc
;
tokenizer.lastIndex = 0;
while (tmp = tokenizer.exec(json)) {
lc = RegExp.leftContext;
rc = RegExp.rightContext;
if (!in_multiline_comment && !in_singleline_comment) {
tmp2 = lc.substring(from);
if (!in_string) {
tmp2 = tmp2.replace(/(\n|\r|\s)*/g,"");
}
new_str[ns++] = tmp2;
}
from = tokenizer.lastIndex;
if (tmp[0] == "\"" && !in_multiline_comment && !in_singleline_comment) {
tmp2 = lc.match(/(\\)*$/);
if (!in_string || !tmp2 || (tmp2[0].length % 2) == 0) { // start of string with ", or unescaped " character found to end string
in_string = !in_string;
}
from--; // include " character in next catch
rc = json.substring(from);
}
else if (tmp[0] == "/*" && !in_string && !in_multiline_comment && !in_singleline_comment) {
in_multiline_comment = true;
}
else if (tmp[0] == "*/" && !in_string && in_multiline_comment && !in_singleline_comment) {
in_multiline_comment = false;
}
else if (tmp[0] == "//" && !in_string && !in_multiline_comment && !in_singleline_comment) {
in_singleline_comment = true;
}
else if ((tmp[0] == "\n" || tmp[0] == "\r") && !in_string && !in_multiline_comment && in_singleline_comment) {
in_singleline_comment = false;
}
else if (!in_multiline_comment && !in_singleline_comment && !(/\n|\r|\s/.test(tmp[0]))) {
new_str[ns++] = tmp[0];
}
}
new_str[ns++] = rc;
return new_str.join("");
};
})(this);

View File

@ -1,52 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
var keywords = 'break case catch continue ' +
'default delete do else false ' +
'for function if in instanceof ' +
'new null return super switch ' +
'this throw true try typeof var while with'
;
var r = SyntaxHighlighter.regexLib;
this.regexList = [
{ regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings
{ regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings
{ regex: r.singleLineCComments, css: 'comments' }, // one line comments
{ regex: r.multiLineCComments, css: 'comments' }, // multiline comments
{ regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion
{ regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords
];
this.forHtmlScript(r.scriptScriptTags);
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['js', 'jscript', 'javascript'];
SyntaxHighlighter.brushes.JScript = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

View File

@ -1,33 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['text', 'plain'];
SyntaxHighlighter.brushes.Plain = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

View File

@ -1,69 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
;(function()
{
// CommonJS
typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null;
function Brush()
{
function process(match, regexInfo)
{
var constructor = SyntaxHighlighter.Match,
code = match[0],
tag = new XRegExp('(&lt;|<)[\\s\\/\\?]*(?<name>[:\\w-\\.]+)', 'xg').exec(code),
result = []
;
if (match.attributes != null)
{
var attributes,
regex = new XRegExp('(?<name> [\\w:\\-\\.]+)' +
'\\s*=\\s*' +
'(?<value> ".*?"|\'.*?\'|\\w+)',
'xg');
while ((attributes = regex.exec(code)) != null)
{
result.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));
result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));
}
}
if (tag != null)
result.push(
new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')
);
return result;
}
this.regexList = [
{ regex: new XRegExp('(\\&lt;|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\&gt;|>)', 'gm'), css: 'color2' }, // <![ ... [ ... ]]>
{ regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // <!-- ... -->
{ regex: new XRegExp('(&lt;|<)[\\s\\/\\?]*(\\w+)(?<attributes>.*?)[\\s\\/\\?]*(&gt;|>)', 'sg'), func: process }
];
};
Brush.prototype = new SyntaxHighlighter.Highlighter();
Brush.aliases = ['xml', 'xhtml', 'xslt', 'html'];
SyntaxHighlighter.brushes.Xml = Brush;
// CommonJS
typeof(exports) != 'undefined' ? exports.Brush = Brush : null;
})();

View File

@ -1,226 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter a,
.syntaxhighlighter div,
.syntaxhighlighter code,
.syntaxhighlighter table,
.syntaxhighlighter table td,
.syntaxhighlighter table tr,
.syntaxhighlighter table tbody,
.syntaxhighlighter table thead,
.syntaxhighlighter table caption,
.syntaxhighlighter textarea {
-moz-border-radius: 0 0 0 0 !important;
-webkit-border-radius: 0 0 0 0 !important;
background: none !important;
border: 0 !important;
bottom: auto !important;
float: none !important;
height: auto !important;
left: auto !important;
line-height: 1.1em !important;
margin: 0 !important;
outline: 0 !important;
overflow: visible !important;
padding: 0 !important;
position: static !important;
right: auto !important;
text-align: left !important;
top: auto !important;
vertical-align: baseline !important;
width: auto !important;
box-sizing: content-box !important;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
font-weight: normal !important;
font-style: normal !important;
font-size: 1em !important;
min-height: inherit !important;
min-height: auto !important;
}
.syntaxhighlighter {
width: 100% !important;
margin: 1em 0 1em 0 !important;
position: relative !important;
overflow: auto !important;
font-size: 1em !important;
}
.syntaxhighlighter.source {
overflow: hidden !important;
}
.syntaxhighlighter .bold {
font-weight: bold !important;
}
.syntaxhighlighter .italic {
font-style: italic !important;
}
.syntaxhighlighter .line {
white-space: pre !important;
}
.syntaxhighlighter table {
width: 100% !important;
}
.syntaxhighlighter table caption {
text-align: left !important;
padding: .5em 0 0.5em 1em !important;
}
.syntaxhighlighter table td.code {
width: 100% !important;
}
.syntaxhighlighter table td.code .container {
position: relative !important;
}
.syntaxhighlighter table td.code .container textarea {
box-sizing: border-box !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
height: 100% !important;
border: none !important;
background: white !important;
padding-left: 1em !important;
overflow: hidden !important;
white-space: pre !important;
}
.syntaxhighlighter table td.gutter .line {
text-align: right !important;
padding: 0 0.5em 0 1em !important;
}
.syntaxhighlighter table td.code .line {
padding: 0 1em !important;
}
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
padding-left: 0em !important;
}
.syntaxhighlighter.show {
display: block !important;
}
.syntaxhighlighter.collapsed table {
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar {
padding: 0.1em 0.8em 0em 0.8em !important;
font-size: 1em !important;
position: static !important;
width: auto !important;
height: auto !important;
}
.syntaxhighlighter.collapsed .toolbar span {
display: inline !important;
margin-right: 1em !important;
}
.syntaxhighlighter.collapsed .toolbar span a {
padding: 0 !important;
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
display: inline !important;
}
.syntaxhighlighter .toolbar {
position: absolute !important;
right: 1px !important;
top: 1px !important;
width: 11px !important;
height: 11px !important;
font-size: 10px !important;
z-index: 10 !important;
}
.syntaxhighlighter .toolbar span.title {
display: inline !important;
}
.syntaxhighlighter .toolbar a {
display: block !important;
text-align: center !important;
text-decoration: none !important;
padding-top: 1px !important;
}
.syntaxhighlighter .toolbar a.expandSource {
display: none !important;
}
.syntaxhighlighter.ie {
font-size: .9em !important;
padding: 1px 0 1px 0 !important;
}
.syntaxhighlighter.ie .toolbar {
line-height: 8px !important;
}
.syntaxhighlighter.ie .toolbar a {
padding-top: 0px !important;
}
.syntaxhighlighter.printing .line.alt1 .content,
.syntaxhighlighter.printing .line.alt2 .content,
.syntaxhighlighter.printing .line.highlighted .number,
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
background: none !important;
}
.syntaxhighlighter.printing .line .number {
color: #bbbbbb !important;
}
.syntaxhighlighter.printing .line .content {
color: black !important;
}
.syntaxhighlighter.printing .toolbar {
display: none !important;
}
.syntaxhighlighter.printing a {
text-decoration: none !important;
}
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
color: black !important;
}
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
color: #008200 !important;
}
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
color: blue !important;
}
.syntaxhighlighter.printing .keyword {
color: #006699 !important;
font-weight: bold !important;
}
.syntaxhighlighter.printing .preprocessor {
color: gray !important;
}
.syntaxhighlighter.printing .variable {
color: #aa7700 !important;
}
.syntaxhighlighter.printing .value {
color: #009900 !important;
}
.syntaxhighlighter.printing .functions {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .constants {
color: #0066cc !important;
}
.syntaxhighlighter.printing .script {
font-weight: bold !important;
}
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
color: gray !important;
}
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
color: red !important;
}
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
color: black !important;
}

File diff suppressed because one or more lines are too long

View File

@ -1,328 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter a,
.syntaxhighlighter div,
.syntaxhighlighter code,
.syntaxhighlighter table,
.syntaxhighlighter table td,
.syntaxhighlighter table tr,
.syntaxhighlighter table tbody,
.syntaxhighlighter table thead,
.syntaxhighlighter table caption,
.syntaxhighlighter textarea {
-moz-border-radius: 0 0 0 0 !important;
-webkit-border-radius: 0 0 0 0 !important;
background: none !important;
border: 0 !important;
bottom: auto !important;
float: none !important;
height: auto !important;
left: auto !important;
line-height: 1.1em !important;
margin: 0 !important;
outline: 0 !important;
overflow: visible !important;
padding: 0 !important;
position: static !important;
right: auto !important;
text-align: left !important;
top: auto !important;
vertical-align: baseline !important;
width: auto !important;
box-sizing: content-box !important;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace !important;
font-weight: normal !important;
font-style: normal !important;
font-size: 1em !important;
min-height: inherit !important;
min-height: auto !important;
}
.syntaxhighlighter {
width: 100% !important;
margin: 1em 0 1em 0 !important;
position: relative !important;
overflow: auto !important;
font-size: 1em !important;
}
.syntaxhighlighter.source {
overflow: hidden !important;
}
.syntaxhighlighter .bold {
font-weight: bold !important;
}
.syntaxhighlighter .italic {
font-style: italic !important;
}
.syntaxhighlighter .line {
white-space: pre !important;
}
.syntaxhighlighter table {
width: 100% !important;
}
.syntaxhighlighter table caption {
text-align: left !important;
padding: .5em 0 0.5em 1em !important;
}
.syntaxhighlighter table td.code {
width: 100% !important;
}
.syntaxhighlighter table td.code .container {
position: relative !important;
}
.syntaxhighlighter table td.code .container textarea {
box-sizing: border-box !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
height: 100% !important;
border: none !important;
background: white !important;
padding-left: 1em !important;
overflow: hidden !important;
white-space: pre !important;
}
.syntaxhighlighter table td.gutter .line {
text-align: right !important;
padding: 0 0.5em 0 1em !important;
}
.syntaxhighlighter table td.code .line {
padding: 0 1em !important;
}
.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
padding-left: 0em !important;
}
.syntaxhighlighter.show {
display: block !important;
}
.syntaxhighlighter.collapsed table {
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar {
padding: 0.1em 0.8em 0em 0.8em !important;
font-size: 1em !important;
position: static !important;
width: auto !important;
height: auto !important;
}
.syntaxhighlighter.collapsed .toolbar span {
display: inline !important;
margin-right: 1em !important;
}
.syntaxhighlighter.collapsed .toolbar span a {
padding: 0 !important;
display: none !important;
}
.syntaxhighlighter.collapsed .toolbar span a.expandSource {
display: inline !important;
}
.syntaxhighlighter .toolbar {
position: absolute !important;
right: 1px !important;
top: 1px !important;
width: 11px !important;
height: 11px !important;
font-size: 10px !important;
z-index: 10 !important;
}
.syntaxhighlighter .toolbar span.title {
display: inline !important;
}
.syntaxhighlighter .toolbar a {
display: block !important;
text-align: center !important;
text-decoration: none !important;
padding-top: 1px !important;
}
.syntaxhighlighter .toolbar a.expandSource {
display: none !important;
}
.syntaxhighlighter.ie {
font-size: .9em !important;
padding: 1px 0 1px 0 !important;
}
.syntaxhighlighter.ie .toolbar {
line-height: 8px !important;
}
.syntaxhighlighter.ie .toolbar a {
padding-top: 0px !important;
}
.syntaxhighlighter.printing .line.alt1 .content,
.syntaxhighlighter.printing .line.alt2 .content,
.syntaxhighlighter.printing .line.highlighted .number,
.syntaxhighlighter.printing .line.highlighted.alt1 .content,
.syntaxhighlighter.printing .line.highlighted.alt2 .content {
background: none !important;
}
.syntaxhighlighter.printing .line .number {
color: #bbbbbb !important;
}
.syntaxhighlighter.printing .line .content {
color: black !important;
}
.syntaxhighlighter.printing .toolbar {
display: none !important;
}
.syntaxhighlighter.printing a {
text-decoration: none !important;
}
.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
color: black !important;
}
.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
color: #008200 !important;
}
.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
color: blue !important;
}
.syntaxhighlighter.printing .keyword {
color: #006699 !important;
font-weight: bold !important;
}
.syntaxhighlighter.printing .preprocessor {
color: gray !important;
}
.syntaxhighlighter.printing .variable {
color: #aa7700 !important;
}
.syntaxhighlighter.printing .value {
color: #009900 !important;
}
.syntaxhighlighter.printing .functions {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .constants {
color: #0066cc !important;
}
.syntaxhighlighter.printing .script {
font-weight: bold !important;
}
.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
color: gray !important;
}
.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
color: red !important;
}
.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
color: black !important;
}
.syntaxhighlighter {
background-color: white !important;
}
.syntaxhighlighter .line.alt1 {
background-color: white !important;
}
.syntaxhighlighter .line.alt2 {
background-color: white !important;
}
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
background-color: #e0e0e0 !important;
}
.syntaxhighlighter .line.highlighted.number {
color: black !important;
}
.syntaxhighlighter table caption {
color: black !important;
}
.syntaxhighlighter .gutter {
color: #afafaf !important;
}
.syntaxhighlighter .gutter .line {
border-right: 3px solid #6ce26c !important;
}
.syntaxhighlighter .gutter .line.highlighted {
background-color: #6ce26c !important;
color: white !important;
}
.syntaxhighlighter.printing .line .content {
border: none !important;
}
.syntaxhighlighter.collapsed {
overflow: visible !important;
}
.syntaxhighlighter.collapsed .toolbar {
color: blue !important;
background: white !important;
border: 1px solid #6ce26c !important;
}
.syntaxhighlighter.collapsed .toolbar a {
color: blue !important;
}
.syntaxhighlighter.collapsed .toolbar a:hover {
color: red !important;
}
.syntaxhighlighter .toolbar {
color: white !important;
background: #6ce26c !important;
border: none !important;
}
.syntaxhighlighter .toolbar a {
color: white !important;
}
.syntaxhighlighter .toolbar a:hover {
color: black !important;
}
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
color: black !important;
}
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
color: #008200 !important;
}
.syntaxhighlighter .string, .syntaxhighlighter .string a {
color: blue !important;
}
.syntaxhighlighter .keyword {
color: #006699 !important;
}
.syntaxhighlighter .preprocessor {
color: gray !important;
}
.syntaxhighlighter .variable {
color: #aa7700 !important;
}
.syntaxhighlighter .value {
color: #009900 !important;
}
.syntaxhighlighter .functions {
color: #ff1493 !important;
}
.syntaxhighlighter .constants {
color: #0066cc !important;
}
.syntaxhighlighter .script {
font-weight: bold !important;
color: #006699 !important;
background-color: none !important;
}
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
color: gray !important;
}
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
color: red !important;
}
.syntaxhighlighter .keyword {
font-weight: bold !important;
}

View File

@ -1,117 +0,0 @@
/**
* SyntaxHighlighter
* http://alexgorbatchev.com/SyntaxHighlighter
*
* SyntaxHighlighter is donationware. If you are using it, please donate.
* http://alexgorbatchev.com/SyntaxHighlighter/donate.html
*
* @version
* 3.0.83 (July 02 2010)
*
* @copyright
* Copyright (C) 2004-2010 Alex Gorbatchev.
*
* @license
* Dual licensed under the MIT and GPL licenses.
*/
.syntaxhighlighter {
background-color: white !important;
}
.syntaxhighlighter .line.alt1 {
background-color: white !important;
}
.syntaxhighlighter .line.alt2 {
background-color: white !important;
}
.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
background-color: #e0e0e0 !important;
}
.syntaxhighlighter .line.highlighted.number {
color: black !important;
}
.syntaxhighlighter table caption {
color: black !important;
}
.syntaxhighlighter .gutter {
color: #afafaf !important;
}
.syntaxhighlighter .gutter .line {
border-right: 3px solid #6ce26c !important;
}
.syntaxhighlighter .gutter .line.highlighted {
background-color: #6ce26c !important;
color: white !important;
}
.syntaxhighlighter.printing .line .content {
border: none !important;
}
.syntaxhighlighter.collapsed {
overflow: visible !important;
}
.syntaxhighlighter.collapsed .toolbar {
color: blue !important;
background: white !important;
border: 1px solid #6ce26c !important;
}
.syntaxhighlighter.collapsed .toolbar a {
color: blue !important;
}
.syntaxhighlighter.collapsed .toolbar a:hover {
color: red !important;
}
.syntaxhighlighter .toolbar {
color: white !important;
background: #6ce26c !important;
border: none !important;
}
.syntaxhighlighter .toolbar a {
color: white !important;
}
.syntaxhighlighter .toolbar a:hover {
color: black !important;
}
.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
color: black !important;
}
.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
color: #008200 !important;
}
.syntaxhighlighter .string, .syntaxhighlighter .string a {
color: blue !important;
}
.syntaxhighlighter .keyword {
color: #006699 !important;
}
.syntaxhighlighter .preprocessor {
color: gray !important;
}
.syntaxhighlighter .variable {
color: #aa7700 !important;
}
.syntaxhighlighter .value {
color: #009900 !important;
}
.syntaxhighlighter .functions {
color: #ff1493 !important;
}
.syntaxhighlighter .constants {
color: #0066cc !important;
}
.syntaxhighlighter .script {
font-weight: bold !important;
color: #006699 !important;
background-color: none !important;
}
.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
color: gray !important;
}
.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
color: #ff1493 !important;
}
.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
color: red !important;
}
.syntaxhighlighter .keyword {
font-weight: bold !important;
}

View File

@ -0,0 +1,21 @@
package ca.uhn.fhir.context;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.ValueSet;
public class FhirContextTest {
@Test
public void testIncrementalScan() {
FhirContext ctx = new FhirContext();
ctx.getResourceDefinition(ValueSet.class);
ctx.getResourceDefinition(Patient.class);
}
}

View File

@ -88,6 +88,7 @@ public class JsonParserTest {
@Test
public void testParseEmptyNarrative() throws ConfigurationException, DataFormatException, IOException {
//@formatter:off
String text = "{\n" +
" \"resourceType\" : \"Patient\",\n" +
" \"extension\" : [\n" +
@ -100,11 +101,13 @@ public class JsonParserTest {
" \"div\" : \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\"\n" +
" }" +
"}";
//@formatter:on
IResource res = ourCtx.newJsonParser().parseResource(text);
}
@Test
public void testNestedContainedResources() {

View File

@ -34,6 +34,7 @@ import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -72,12 +73,15 @@ public class GenericClientTest {
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), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] {new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "/Patient/44/_history/22")});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
client.create(p1);
MethodOutcome outcome = client.create(p1);
assertEquals("44",outcome.getId().getIdPart());
assertEquals("22",outcome.getId().getVersionIdPart());
assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString());
assertEquals("POST", capt.getValue().getMethod());

View File

@ -0,0 +1,151 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.gzip.GzipHandler;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class CompressionTest {
private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompressionTest.class);
private static int ourPort;
private static Server ourServer;
private static FhirContext ourCtx;
public static String decompress(byte[] theResource) {
GZIPInputStream is;
try {
is = new GZIPInputStream(new ByteArrayInputStream(theResource));
return IOUtils.toString(is, "UTF-8");
} catch (IOException e) {
throw new DataFormatException("Failed to decompress contents", e);
}
}
@Test
public void testRead() throws Exception {
{
/*
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
httpGet.addHeader(Constants.HEADER_ACCEPT_ENCODING, "gzip");
HttpResponse status = ourClient.execute(httpGet);
Header ce = status.getFirstHeader(Constants.HEADER_CONTENT_ENCODING);
*/
URLConnection c = new URL("http://localhost:" + ourPort + "/Patient/1").openConnection();
c.addRequestProperty(Constants.HEADER_ACCEPT_ENCODING, "gzip");
String enc = c.getContentEncoding();
assertEquals("gzip", enc);
byte[] responseContentBytes = IOUtils.toByteArray(c.getInputStream());
String responseContent = decompress(responseContentBytes);
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class,responseContent).getIdentifierFirstRep();
assertEquals("1", dt.getSystem().getValueAsString());
assertEquals(null, dt.getValue().getValueAsString());
}
}
@Test
public void testVRead() throws Exception {
{
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1/_history/2");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
IdentifierDt dt = ourCtx.newXmlParser().parseResource(Patient.class,responseContent).getIdentifierFirstRep();
assertEquals("1", dt.getSystem().getValueAsString());
assertEquals("2", dt.getValue().getValueAsString());
}
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
DummyProvider patientProvider = new DummyProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
ourCtx = servlet.getFhirContext();
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
// GzipHandler gh = new GzipHandler();
// gh.setHandler(proxyHandler);
// gh.setMimeTypes(Constants.CT_ATOM_XML+','+Constants.CT_FHIR_JSON+','+Constants.CT_FHIR_XML);
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyProvider implements IResourceProvider {
@Read(version = true)
public Patient findPatient(@IdParam IdDt theId) {
Patient patient = new Patient();
patient.addIdentifier(theId.getIdPart(), theId.getVersionIdPart());
patient.setId("Patient/1/_history/1");
return patient;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
}
}

View File

@ -52,7 +52,7 @@ public class PagingTest {
String link;
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base+ "/Patient?");
HttpGet httpGet = new HttpGet(base+ "/Patient?_format=xml&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
@ -60,7 +60,7 @@ public class PagingTest {
assertEquals(5, bundle.getEntries().size());
assertEquals("0", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("4", bundle.getEntries().get(4).getId().getIdPart());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=5&" + Constants.PARAM_COUNT + "=5", bundle.getLinkNext().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=5&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkNext().getValue());
assertNull(bundle.getLinkPrevious().getValue());
link=bundle.getLinkNext().getValue();
}
@ -74,7 +74,30 @@ public class PagingTest {
assertEquals("5", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("9", bundle.getEntries().get(4).getId().getIdPart());
assertNull(bundle.getLinkNext().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=5", bundle.getLinkPrevious().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=0&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkPrevious().getValue());
}
}
@Test
public void testSearchInexactOffset() throws Exception {
when(myPagingProvider.getDefaultPageSize()).thenReturn(5);
when(myPagingProvider.getMaximumPageSize()).thenReturn(9);
when(myPagingProvider.storeResultList(any(IBundleProvider.class))).thenReturn("ABCD");
when(myPagingProvider.retrieveResultList(eq("ABCD"))).thenReturn(ourBundleProvider);
String base = "http://localhost:" + ourPort;
{
HttpGet httpGet = new HttpGet(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=8&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourContext.newXmlParser().parseBundle(responseContent);
assertEquals(2, bundle.getEntries().size());
assertEquals("8", bundle.getEntries().get(0).getId().getIdPart());
assertEquals("9", bundle.getEntries().get(1).getId().getIdPart());
assertNull(bundle.getLinkNext().getValue());
assertEquals(base + '?'+Constants.PARAM_PAGINGACTION + "=ABCD&" + Constants.PARAM_PAGINGOFFSET + "=3&" + Constants.PARAM_COUNT + "=5&_format=xml&_pretty=true", bundle.getLinkPrevious().getValue());
}
}

View File

@ -1,263 +0,0 @@
package ca.uhn.fhir.rest.server;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.tester.RestfulServerTesterServlet;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
public class TesterTest {
private int myPort;
private Server myServer;
private FhirContext myCtx;
private RestfulServer myRestfulServer;
@Before
public void before() throws Exception {
myPort = RandomServerPortProvider.findFreePort();
myPort = 8888;
myServer = new Server(myPort);
myCtx = new FhirContext();
myCtx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
myRestfulServer = new RestfulServer(myCtx);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
RestfulServerTesterServlet testerServlet = new RestfulServerTesterServlet();
testerServlet.setServerBase("http://localhost:" + myPort + "/fhir/context");
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
ServletHolder handler = new ServletHolder();
handler.setServlet(testerServlet);
proxyHandler.addServlet(handler, "/fhir/tester/*");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(myRestfulServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
myServer.setHandler(proxyHandler);
}
@After
public void after() throws Exception {
myServer.stop();
}
@Test
public void testTester() throws Exception {
if (true) return;
myRestfulServer.setProviders(new SearchProvider(), new GlobalHistoryProvider());
myServer.start();
Thread.sleep(9999999L);
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class SearchProvider {
private int myNextId = 1;
private HashMap<String, Patient> myIdToPatient;
private static Patient createPatient() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
return patient;
}
public SearchProvider() {
myIdToPatient = new HashMap<String, Patient>();
{
Patient patient = createPatient();
myIdToPatient.put("" + myNextId++, patient);
}
{
Patient patient = new Patient();
patient.getIdentifier().add(new IdentifierDt());
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00002");
patient.getName().add(new HumanNameDt());
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.getGender().setText("F");
myIdToPatient.put("" + myNextId++, patient);
}
}
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
IdDt id = new IdDt(myNextId++);
myIdToPatient.put(id.getValueAsString(), thePatient);
return new MethodOutcome(id);
}
@SuppressWarnings("unused")
@Validate
public MethodOutcome validatePatient(@ResourceParam Patient thePatient) {
MethodOutcome outcome = new MethodOutcome();
outcome.setOperationOutcome(new OperationOutcome());
outcome.getOperationOutcome().addIssue().setDetails("This is a detected issue");
return outcome;
}
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {
myIdToPatient.put(theId.getValueAsString(), thePatient);
return new MethodOutcome(theId);
}
@Delete(type=Patient.class)
public MethodOutcome deletePatient(@IdParam IdDt theId) {
myIdToPatient.remove(theId.getValue());
return new MethodOutcome();
}
@Search(type = Patient.class)
public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number). Example system 'urn:hapitest:mrns', example MRN '00002'") @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
for (Patient next : myIdToPatient.values()) {
for (IdentifierDt nextId : next.getIdentifier()) {
if (nextId.matchesSystemAndValue(theIdentifier)) {
return next;
}
}
}
return null;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Read(type = Patient.class)
public Patient getPatientById(@IdParam IdDt theId) {
return myIdToPatient.get(theId.getValue());
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@History(type = Patient.class)
public List<Patient> getPatientHistory(@IdParam IdDt theId) {
Patient patient = myIdToPatient.get(theId.getValue());
if (patient==null) {
throw new ResourceNotFoundException(Patient.class, theId);
}
ArrayList<Patient> retVal = new ArrayList<Patient>();
for (int i = 0; i < 5;i++) {
Patient pat = createPatient();
pat.setId(theId);
pat.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, ""+i);
retVal.add(pat);
}
return retVal;
}
}
public static class GlobalHistoryProvider {
private InstantDt myLastSince;
private IntegerDt myLastCount;
@History
public List<IResource> getGlobalHistory(@Since InstantDt theSince, @Count IntegerDt theCount) {
myLastSince = theSince;
myLastCount = theCount;
ArrayList<IResource> retVal = new ArrayList<IResource>();
IResource p = SearchProvider.createPatient();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:01"));
retVal.add(p);
p = SearchProvider.createPatient();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("B"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:03"));
retVal.add(p);
p = createOrganization();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2013-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2013-01-01T01:00:01"));
retVal.add(p);
return retVal;
}
}
private static Organization createOrganization() {
Organization retVal = new Organization();
retVal.addIdentifier();
retVal.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
retVal.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
retVal.getIdentifier().get(0).setValue("00001");
retVal.getName().setValue("Test Org");
return retVal;
}
}

View File

@ -1,779 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2011-2013, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Generated on Mon, Feb 3, 2014 23:47+1100 for FHIR v0.80
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://hl7.org/fhir" xmlns:xhtml="http://www.w3.org/1999/xhtml" targetNamespace="http://hl7.org/fhir" elementFormDefault="qualified" version="0.80">
<xs:include schemaLocation="fhir-base.xsd"/>
<xs:element name="Conformance" type="Conformance">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="Conformance">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Resource">
<xs:sequence>
<xs:element name="identifier" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>The identifier that is used to identify this conformance statement when it is referenced in a specification, model, design or an instance (should be globally unique OID, UUID, or URI).</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="version" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>The identifier that is used to identify this version of the conformance statement when it is referenced in a specification, model, design or instance. This is an arbitrary value managed by the profile author manually and the value should be a timestamp.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="name" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>A free text natural language name identifying the conformance statement.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="publisher" minOccurs="1" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Name of Organization publishing this conformance statement.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="telecom" minOccurs="0" maxOccurs="unbounded" type="Contact">
<xs:annotation>
<xs:documentation>Contacts for Organization relevant to this conformance statement. The contacts may be a website, email, phone numbers, etc.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="description" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>A free text natural language description of the conformance statement and its use. Typically, this is used when the profile describes a desired rather than an actual solution, for example as a formal expression of requirements as part of an RFP.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="status" minOccurs="0" maxOccurs="1" type="ConformanceStatementStatus">
<xs:annotation>
<xs:documentation>The status of this conformance statement.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="experimental" minOccurs="0" maxOccurs="1" type="boolean">
<xs:annotation>
<xs:documentation>A flag to indicate that this conformance statement is authored for testing purposes (or education/evaluation/marketing), and is not intended to be used for genuine usage.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="date" minOccurs="1" maxOccurs="1" type="dateTime">
<xs:annotation>
<xs:documentation>The date when the conformance statement was published.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="software" type="Conformance.Software" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Software that is covered by this conformance statement. It is used when the profile describes the capabilities of a particular software version, independent of an installation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="implementation" type="Conformance.Implementation" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Identifies a specific implementation instance that is described by the conformance statement - i.e. a particular installation, rather than the capabilities of a software program.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="fhirVersion" minOccurs="1" maxOccurs="1" type="id">
<xs:annotation>
<xs:documentation>The version of the FHIR specification on which this conformance statement is based.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="acceptUnknown" minOccurs="1" maxOccurs="1" type="boolean">
<xs:annotation>
<xs:documentation>A flag that indicates whether the application accepts unknown elements as part of a resource.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="format" minOccurs="1" maxOccurs="unbounded" type="code">
<xs:annotation>
<xs:documentation>A list of the formats supported by this implementation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="profile" minOccurs="0" maxOccurs="unbounded" type="ResourceReference">
<xs:annotation>
<xs:documentation>A list of profiles supported by the system. For a server, &quot;supported by the system&quot; means the system hosts/produces a set of recourses, conformant to a particular profile, and allows its clients to search using this profile and to find appropriate data. For a client, it means the system will search by this profile and process data according to the guidance implicit in the profile.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="rest" type="Conformance.Rest" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A definition of the restful capabilities of the solution, if any.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="messaging" type="Conformance.Messaging" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A description of the messaging capabilities of the solution.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="document" type="Conformance.Document" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A document definition.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Software">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="name" minOccurs="1" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Name software is known by.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="version" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>The version identifier for the software covered by this statement.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="releaseDate" minOccurs="0" maxOccurs="1" type="dateTime">
<xs:annotation>
<xs:documentation>Date this version of the software released.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Implementation">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="description" minOccurs="1" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Information about the specific installation that this conformance statement relates to.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="url" minOccurs="0" maxOccurs="1" type="uri">
<xs:annotation>
<xs:documentation>A base URL for the implementation. This forms the base for REST interfaces as well as the mailbox and document interfaces.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Rest">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="mode" minOccurs="1" maxOccurs="1" type="RestfulConformanceMode">
<xs:annotation>
<xs:documentation>Identifies whether this portion of the statement is describing ability to initiate or receive restful operations.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Information about the system's restful capabilities that apply across all applications, such as security.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="security" type="Conformance.Security" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Information about security of implementation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="resource" type="Conformance.Resource" minOccurs="1" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A specification of the restful capabilities of the solution for a specific resource type.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="operation" type="Conformance.Operation1" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A specification of restful operations supported by the system.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="query" type="Conformance.Query" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Definition of a named query and its parameters and their meaning.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentMailbox" minOccurs="0" maxOccurs="unbounded" type="uri">
<xs:annotation>
<xs:documentation>A list of profiles that this server implements for accepting documents in the mailbox. If this list is empty, then documents are not accepted. The base specification has the profile identifier &quot;http://hl7.org/fhir/documents/mailbox&quot;. Other specifications can declare their own identifier for this purpose.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Security">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="cors" minOccurs="0" maxOccurs="1" type="boolean">
<xs:annotation>
<xs:documentation>Server adds CORS headers when responding to requests - this enables javascript applications to yuse the server.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="service" minOccurs="0" maxOccurs="unbounded" type="CodeableConcept">
<xs:annotation>
<xs:documentation>Types of security services are supported/required by the system.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="description" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>General description of how security works.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="certificate" type="Conformance.Certificate" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Certificates associated with security profiles.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Certificate">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="type" minOccurs="0" maxOccurs="1" type="code">
<xs:annotation>
<xs:documentation>Mime type for certificate.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="blob" minOccurs="0" maxOccurs="1" type="base64Binary">
<xs:annotation>
<xs:documentation>Actual certificate.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Resource">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="type" minOccurs="1" maxOccurs="1" type="code">
<xs:annotation>
<xs:documentation>A type of resource exposed via the restful interface.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="profile" minOccurs="0" maxOccurs="1" type="ResourceReference">
<xs:annotation>
<xs:documentation>A specification of the profile that describes the solution's support for the resource, including any constraints on cardinality, bindings, lengths or other limitations.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="operation" type="Conformance.Operation" minOccurs="1" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Identifies a restful operation supported by the solution.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="readHistory" minOccurs="0" maxOccurs="1" type="boolean">
<xs:annotation>
<xs:documentation>A flag for whether the server is able to return past versions as part of the vRead operation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="updateCreate" minOccurs="0" maxOccurs="1" type="boolean">
<xs:annotation>
<xs:documentation>A flag to indicate that the server allows the client to create new identities on the server. If the update operation is used (client) or allowed (server) to a new location where a resource doesn't already exist. This means that the server allows the client to create new identities on the server.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="searchInclude" minOccurs="0" maxOccurs="unbounded" type="string">
<xs:annotation>
<xs:documentation>A list of _include values supported by the server.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="searchParam" type="Conformance.SearchParam" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Additional search parameters for implementations to support and/or make use of.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Operation">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="code" minOccurs="1" maxOccurs="1" type="RestfulOperationType">
<xs:annotation>
<xs:documentation>Coded identifier of the operation, supported by the system resource.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Guidance specific to the implementation of this operation, such as 'delete is a logical delete' or 'updates are only allowed with version id' or 'creates permitted from pre-authorized certificates only'.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.SearchParam">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="name" minOccurs="1" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>The name of the search parameter used in the interface.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="definition" minOccurs="0" maxOccurs="1" type="uri">
<xs:annotation>
<xs:documentation>A formal reference to where this parameter was first defined, so that a client can be confident of the meaning of the search parameter.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="type" minOccurs="1" maxOccurs="1" type="SearchParamType">
<xs:annotation>
<xs:documentation>The type of value a search parameter refers to, and how the content is interpreted.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>This allows documentation of any distinct behaviors about how the search parameter is used. For example, text matching algorithms.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="target" minOccurs="0" maxOccurs="unbounded" type="code">
<xs:annotation>
<xs:documentation>Types of resource (if a resource is referenced).</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="chain" minOccurs="0" maxOccurs="unbounded" type="string">
<xs:annotation>
<xs:documentation>Chained names supported.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Operation1">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="code" minOccurs="1" maxOccurs="1" type="RestfulOperationSystem">
<xs:annotation>
<xs:documentation>A coded identifier of the operation, supported by the system.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Guidance specific to the implementation of this operation, such as limitations on the kind of transactions allowed, or information about system wide search is implemented.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Query">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="name" minOccurs="1" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>The name of a query, which is used in the _query parameter when the query is called.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="definition" minOccurs="1" maxOccurs="1" type="uri">
<xs:annotation>
<xs:documentation>Identifies the custom query, defined either in FHIR core or another profile.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Additional information about how the query functions in this particular implementation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="parameter" type="Conformance.SearchParam" minOccurs="0" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Identifies which of the parameters for the named query are supported.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Messaging">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="endpoint" minOccurs="0" maxOccurs="1" type="uri">
<xs:annotation>
<xs:documentation>An address to which messages and/or replies are to be sent.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="reliableCache" minOccurs="0" maxOccurs="1" type="integer">
<xs:annotation>
<xs:documentation>Length if the receiver's reliable messaging cache (if a receiver) or how long the cache length on the receiver should be (if a sender).</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Documentation about the system's messaging capabilities for this endpoint not otherwise documented by the conformance statement. For example, process for becoming an authorized messaging exchange partner.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="event" type="Conformance.Event" minOccurs="1" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>A description of the solution's support for an event at this end point.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Event">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="code" minOccurs="1" maxOccurs="1" type="Coding">
<xs:annotation>
<xs:documentation>A coded identifier of a supported messaging event.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="category" minOccurs="0" maxOccurs="1" type="MessageSignificanceCategory">
<xs:annotation>
<xs:documentation>The impact of the content of the message.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="mode" minOccurs="1" maxOccurs="1" type="ConformanceEventMode">
<xs:annotation>
<xs:documentation>The mode of this event declaration - whether application is sender or receiver.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="protocol" minOccurs="0" maxOccurs="unbounded" type="Coding">
<xs:annotation>
<xs:documentation>A list of the messaging transport protocol(s) identifiers, supported by this endpoint.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="focus" minOccurs="1" maxOccurs="1" type="code">
<xs:annotation>
<xs:documentation>A resource associated with the event. This is the resource that defines the event.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="request" minOccurs="1" maxOccurs="1" type="ResourceReference">
<xs:annotation>
<xs:documentation>Information about the request for this event.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="response" minOccurs="1" maxOccurs="1" type="ResourceReference">
<xs:annotation>
<xs:documentation>Information about the response for this event.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>Guidance on how this event is handled, such as internal system trigger points, business rules, etc.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Conformance.Document">
<xs:annotation>
<xs:documentation>A conformance statement is a set of requirements for a desired implementation or a description of how a target application fulfills those requirements in a particular implementation.</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="BackboneElement">
<xs:sequence>
<xs:element name="mode" minOccurs="1" maxOccurs="1" type="DocumentMode">
<xs:annotation>
<xs:documentation>Mode of this document declaration - whether application is producer or consumer.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="documentation" minOccurs="0" maxOccurs="1" type="string">
<xs:annotation>
<xs:documentation>A description of how the application supports or uses the specified document profile. For example, when are documents created, what action is taken with consumed documents, etc.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="profile" minOccurs="1" maxOccurs="1" type="ResourceReference">
<xs:annotation>
<xs:documentation>A constraint on a resource used in the document.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="DocumentMode-list">
<xs:restriction base="xs:string">
<xs:enumeration value="producer">
<xs:annotation>
<xs:documentation>The application produces documents of the specified type.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="consumer">
<xs:annotation>
<xs:documentation>The application consumes documents of the specified type.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="DocumentMode">
<xs:annotation>
<xs:documentation>Whether the application produces or consumes documents</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="DocumentMode-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="RestfulConformanceMode-list">
<xs:restriction base="xs:string">
<xs:enumeration value="client">
<xs:annotation>
<xs:documentation>The application acts as a server for this resource.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="server">
<xs:annotation>
<xs:documentation>The application acts as a client for this resource.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="RestfulConformanceMode">
<xs:annotation>
<xs:documentation>The mode of a RESTful conformance statement</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="RestfulConformanceMode-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="ConformanceEventMode-list">
<xs:restriction base="xs:string">
<xs:enumeration value="sender">
<xs:annotation>
<xs:documentation>The application sends requests and receives responses.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="receiver">
<xs:annotation>
<xs:documentation>The application receives requests and sends responses.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ConformanceEventMode">
<xs:annotation>
<xs:documentation>The mode of a message conformance statement</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="ConformanceEventMode-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="MessageSignificanceCategory-list">
<xs:restriction base="xs:string">
<xs:enumeration value="Consequence">
<xs:annotation>
<xs:documentation>The message represents/requests a change that should not be processed more than once. E.g. Making a booking for an appointment.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Currency">
<xs:annotation>
<xs:documentation>The message represents a response to query for current information. Retrospective processing is wrong and/or wasteful.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Notification">
<xs:annotation>
<xs:documentation>The content is not necessarily intended to be current, and it can be reprocessed, though there may be version issues created by processing old notifications.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="MessageSignificanceCategory">
<xs:annotation>
<xs:documentation>The impact of the content of a message</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="MessageSignificanceCategory-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="RestfulOperationType-list">
<xs:restriction base="xs:string">
<xs:enumeration value="read">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="vread">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="update">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="delete">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="history-instance">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="validate">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="history-type">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="create">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="search-type">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="RestfulOperationType">
<xs:annotation>
<xs:documentation>Operations supported by REST at the type or instance level</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="RestfulOperationType-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="ConformanceStatementStatus-list">
<xs:restriction base="xs:string">
<xs:enumeration value="draft">
<xs:annotation>
<xs:documentation>This conformance statement is still under development.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="active">
<xs:annotation>
<xs:documentation>This conformance statement is ready for use in production systems.</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="retired">
<xs:annotation>
<xs:documentation>This conformance statement has been withdrawn or superceded and should no longer be used.</xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="ConformanceStatementStatus">
<xs:annotation>
<xs:documentation>The status of this conformance statement</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="ConformanceStatementStatus-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="RestfulOperationSystem-list">
<xs:restriction base="xs:string">
<xs:enumeration value="transaction">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="search-system">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="history-system">
<xs:annotation>
<xs:documentation></xs:documentation>
</xs:annotation>
</xs:enumeration>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="RestfulOperationSystem">
<xs:annotation>
<xs:documentation>Operations supported by REST at the system level</xs:documentation>
<xs:documentation>If the element is present, it must have either a @value, an @id, or extensions</xs:documentation>
</xs:annotation>
<xs:complexContent>
<xs:extension base="Element">
<xs:attribute name="value" type="RestfulOperationSystem-list" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?><Patient xmlns="http://hl7.org/fhir">
<text>
<status value="generated"/>

View File

@ -18,9 +18,10 @@
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>

View File

@ -6,6 +6,11 @@
<project>hapi-fhir-base</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
@ -16,9 +21,17 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
</natures>
</projectDescription>

View File

@ -1,7 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="hapi-fhir-jpaserver-base">
<wb-resource deploy-path="/" source-path="/src/main/java"/>
<wb-resource deploy-path="/" source-path="/src/main/resources"/>
</wb-module>
</project-modules>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<installed facet="java" version="1.6"/>
<installed facet="jst.utility" version="1.0"/>
</faceted-project>

View File

@ -8,7 +8,6 @@
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-base</artifactId>
<packaging>jar</packaging>
@ -24,7 +23,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
<version>${logback-version}</version>
<scope>test</scope>
</dependency>
<dependency>
@ -37,13 +36,7 @@
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>${hamcrest_version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
<!-- Test Database -->
@ -51,7 +44,10 @@
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby_version}</version>
<scope>test</scope>
</dependency>
<!-- <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.2</version> </dependency> -->
<!-- Spring -->
@ -156,18 +152,6 @@
<properties>
<junit_version>4.11</junit_version>
<derby_version>10.10.2.0</derby_version>
<guava_version>14.0.1</guava_version>
<hamcrest_version>1.3</hamcrest_version>
<!-- <hibernate_version>4.3.5.Final</hibernate_version>-->
<hibernate_version>4.2.12.Final</hibernate_version>
<hibernate_validator_version>5.1.0.Final</hibernate_validator_version>
<jetty_version>9.1.1.v20140108</jetty_version>
<mockito_version>1.9.5</mockito_version>
<slf4j_version>1.7.2</slf4j_version>
<spring_version>4.0.1.RELEASE</spring_version>
<skip-hib4>false</skip-hib4>
</properties>

View File

@ -5,7 +5,6 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.io.UnsupportedEncodingException;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@ -28,7 +27,6 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import ca.uhn.fhir.context.ConfigurationException;
@ -79,30 +77,31 @@ import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
public abstract class BaseFhirDao {
@Autowired(required=true)
@Autowired(required = true)
private FhirContext myContext;
@PersistenceContext(name = "FHIR_UT", type = PersistenceContextType.TRANSACTION, unitName = "FHIR_UT")
private EntityManager myEntityManager;
@Autowired
private List<IFhirResourceDao<?>> myResourceDaos;
private Map<Class<? extends IResource>, IFhirResourceDao<?>> myResourceTypeToDao;
protected void loadResourcesById(Set<IdDt> theIncludePids, List<IResource> theResourceListToPopulate) {
Set<Long> pids = new HashSet<Long>();
for (IdDt next : theIncludePids) {
pids.add(next.getIdPartAsLong());
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
// cq.where(builder.equal(from.get("myResourceType"), getContext().getResourceDefinition(myResourceType).getName()));
// if (theIncludePids != null) {
cq.where(from.get("myId").in(pids));
// }
// cq.where(builder.equal(from.get("myResourceType"),
// getContext().getResourceDefinition(myResourceType).getName()));
// if (theIncludePids != null) {
cq.where(from.get("myId").in(pids));
// }
TypedQuery<ResourceTable> q = myEntityManager.createQuery(cq);
for (ResourceTable next : q.getResultList()) {
@ -193,7 +192,7 @@ public abstract class BaseFhirDao {
}
}
private void searchHistoryHistory(String theResourceName, Long theResourceId, Date theSince,Date theEnd, Integer theLimit, List<HistoryTuple> tuples) {
private void searchHistoryHistory(String theResourceName, Long theResourceId, Date theSince, Date theEnd, Integer theLimit, List<HistoryTuple> tuples) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = builder.createTupleQuery();
Root<?> from = cq.from(ResourceHistoryTable.class);
@ -232,12 +231,12 @@ public abstract class BaseFhirDao {
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirDao.class);
private void searchHistoryHistory(List<HistoryTuple> theTuples, List<BaseHasResource> theRetVal) {
Collection<HistoryTuple> tuples = Collections2.filter(theTuples, new com.google.common.base.Predicate<HistoryTuple>() {
@Override
public boolean apply(HistoryTuple theInput) {
return theInput.isHistory()==true;
return theInput.isHistory() == true;
}
});
Collection<Long> ids = Collections2.transform(tuples, new Function<HistoryTuple, Long>() {
@ -249,7 +248,7 @@ public abstract class BaseFhirDao {
if (ids.isEmpty()) {
return;
}
ourLog.info("Retrieving {} history elements from ResourceHistoryTable", ids.size());
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
@ -423,8 +422,7 @@ public abstract class BaseFhirDao {
if (nextObject instanceof QuantityDt) {
QuantityDt nextValue = (QuantityDt) nextObject;
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(),
nextValue.getUnits().getValue());
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(), nextValue.getUnits().getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
@ -507,8 +505,7 @@ public abstract class BaseFhirDao {
} else if (nextObject instanceof ContactDt) {
ContactDt nextContact = (ContactDt) nextObject;
if (nextContact.getValue().isEmpty() == false) {
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextContact.getValue().getValueAsString()), nextContact
.getValue().getValueAsString());
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextContact.getValue().getValueAsString()), nextContact.getValue().getValueAsString());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
}
@ -610,19 +607,19 @@ public abstract class BaseFhirDao {
protected IBundleProvider history(String theResourceName, Long theId, Date theSince) {
final List<HistoryTuple> tuples = new ArrayList<HistoryTuple>();
final InstantDt end = createHistoryToTimestamp();
StopWatch timer = new StopWatch();
int limit = 10000;
// Get list of IDs
searchHistoryCurrentVersion(theResourceName, theId, theSince, end.getValue(), limit, tuples);
assert tuples.size() < 2 || !tuples.get(tuples.size() - 2).getUpdated().before(tuples.get(tuples.size() - 1).getUpdated());
ourLog.info("Retrieved {} history IDs from current versions in {} ms", tuples.size(), timer.getMillisAndRestart());
searchHistoryHistory(theResourceName, theId, theSince, end.getValue() , limit, tuples);
searchHistoryHistory(theResourceName, theId, theSince, end.getValue(), limit, tuples);
assert tuples.size() < 2 || !tuples.get(tuples.size() - 2).getUpdated().before(tuples.get(tuples.size() - 1).getUpdated());
ourLog.info("Retrieved {} history IDs from previous versions in {} ms", tuples.size(), timer.getMillisAndRestart());
@ -631,21 +628,21 @@ public abstract class BaseFhirDao {
assert tuples.size() < 2 || !tuples.get(tuples.size() - 2).getUpdated().before(tuples.get(tuples.size() - 1).getUpdated());
return new IBundleProvider() {
@Override
public int size() {
return tuples.size();
}
@Override
public List<IResource> getResources(int theFromIndex, int theToIndex) {
StopWatch timer = new StopWatch();
List<BaseHasResource> resEntities = Lists.newArrayList();
List<HistoryTuple> tupleSubList = tuples.subList(theFromIndex, theToIndex);
searchHistoryCurrentVersion(tupleSubList, resEntities);
ourLog.info("Loaded history from current versions in {} ms", timer.getMillisAndRestart());
searchHistoryHistory(tupleSubList, resEntities);
ourLog.info("Loaded history from previous versions in {} ms", timer.getMillisAndRestart());
@ -656,7 +653,7 @@ public abstract class BaseFhirDao {
}
});
int limit = theToIndex-theFromIndex;
int limit = theToIndex - theFromIndex;
if (resEntities.size() > limit) {
resEntities = resEntities.subList(0, limit);
}
@ -667,7 +664,7 @@ public abstract class BaseFhirDao {
}
return retVal;
}
@Override
public InstantDt getPublished() {
return end;
@ -676,7 +673,8 @@ public abstract class BaseFhirDao {
}
InstantDt createHistoryToTimestamp() {
// final InstantDt end = new InstantDt(DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.SECOND), -1));
// final InstantDt end = new InstantDt(DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.SECOND),
// -1));
return InstantDt.withCurrentTime();
}
@ -716,22 +714,31 @@ public abstract class BaseFhirDao {
theEntity.setUpdated(new Date());
theEntity.setResourceType(toResourceName(theResource));
List<ResourceReferenceDt> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, ResourceReferenceDt.class);
for (ResourceReferenceDt nextRef : refs) {
if (nextRef.getReference().isEmpty()==false) {
if (nextRef.getReference().hasVersionIdPart()) {
nextRef.setReference(nextRef.getReference().toUnqualifiedVersionless());
}
}
}
String encoded = myConfig.getResourceEncoding().newParser(myContext).encodeResourceToString(theResource);
ResourceEncodingEnum encoding = myConfig.getResourceEncoding();
ResourceEncodingEnum encoding = myConfig.getResourceEncoding();
theEntity.setEncoding(encoding);
try {
switch (encoding) {
case JSON:
switch (encoding) {
case JSON:
theEntity.setResource(encoded.getBytes("UTF-8"));
break;
case JSONC:
theEntity.setResource(GZipUtil.compress(encoded));
break;
}
break;
case JSONC:
theEntity.setResource(GZipUtil.compress(encoded));
break;
}
} catch (UnsupportedEncodingException e) {
}
TagList tagList = (TagList) theResource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
if (tagList != null) {
for (Tag next : tagList) {
@ -756,7 +763,7 @@ public abstract class BaseFhirDao {
}
protected <T extends IResource> T toResource(Class<T> theResourceType, BaseHasResource theEntity) {
String resourceText=null;
String resourceText = null;
switch (theEntity.getEncoding()) {
case JSON:
try {
@ -769,13 +776,18 @@ public abstract class BaseFhirDao {
resourceText = GZipUtil.decompress(theEntity.getResource());
break;
}
IParser parser = theEntity.getEncoding().newParser(getContext());
T retVal = parser.parseResource(theResourceType, resourceText);
retVal.setId(theEntity.getIdDt());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, theEntity.getVersion());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished());
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated());
if (theEntity.getDeleted()!=null) {
ResourceMetadataKeyEnum.DELETED_AT.put(retVal, new InstantDt(theEntity.getDeleted()));
}
Collection<? extends BaseTag> tags = theEntity.getTags();
if (tags.size() > 0) {
TagList tagList = new TagList();
@ -795,7 +807,7 @@ public abstract class BaseFhirDao {
return myContext.getResourceDefinition(theResourceType).getName();
}
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory) {
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, boolean theDelete) {
if (entity.getPublished() == null) {
entity.setPublished(new Date());
}
@ -813,25 +825,43 @@ public abstract class BaseFhirDao {
Collection<ResourceIndexedSearchParamDate> paramsDate = new ArrayList<ResourceIndexedSearchParamDate>(entity.getParamsDate());
Collection<ResourceLink> resourceLinks = new ArrayList<ResourceLink>(entity.getResourceLinks());
final List<ResourceIndexedSearchParamString> stringParams = extractSearchParamStrings(entity, theResource);
final List<ResourceIndexedSearchParamToken> tokenParams = extractSearchParamTokens(entity, theResource);
final List<ResourceIndexedSearchParamNumber> numberParams = extractSearchParamNumber(entity, theResource);
final List<ResourceIndexedSearchParamDate> dateParams = extractSearchParamDates(entity, theResource);
final List<ResourceLink> links = extractResourceLinks(entity, theResource);
final List<ResourceIndexedSearchParamString> stringParams;
final List<ResourceIndexedSearchParamToken> tokenParams;
final List<ResourceIndexedSearchParamNumber> numberParams;
final List<ResourceIndexedSearchParamDate> dateParams;
final List<ResourceLink> links;
if (theDelete) {
stringParams = Collections.emptyList();
tokenParams = Collections.emptyList();
numberParams = Collections.emptyList();
dateParams = Collections.emptyList();
links = Collections.emptyList();
entity.setDeleted(new Date());
entity.setUpdated(new Date());
} else {
populateResourceIntoEntity(theResource, entity);
stringParams = extractSearchParamStrings(entity, theResource);
tokenParams = extractSearchParamTokens(entity, theResource);
numberParams = extractSearchParamNumber(entity, theResource);
dateParams = extractSearchParamDates(entity, theResource);
links = extractResourceLinks(entity, theResource);
populateResourceIntoEntity(theResource, entity);
entity.setUpdated(new Date());
entity.setParamsString(stringParams);
entity.setParamsStringPopulated(stringParams.isEmpty()==false);
entity.setParamsToken(tokenParams);
entity.setParamsTokenPopulated(tokenParams.isEmpty()==false);
entity.setParamsNumber(numberParams);
entity.setParamsNumberPopulated(numberParams.isEmpty()==false);
entity.setParamsDate(dateParams);
entity.setParamsDatePopulated(dateParams.isEmpty()==false);
entity.setResourceLinks(links);
entity.setHasLinks(links.isEmpty()==false);
entity.setUpdated(new Date());
entity.setParamsString(stringParams);
entity.setParamsStringPopulated(stringParams.isEmpty() == false);
entity.setParamsToken(tokenParams);
entity.setParamsTokenPopulated(tokenParams.isEmpty() == false);
entity.setParamsNumber(numberParams);
entity.setParamsNumberPopulated(numberParams.isEmpty() == false);
entity.setParamsDate(dateParams);
entity.setParamsDatePopulated(dateParams.isEmpty() == false);
entity.setResourceLinks(links);
entity.setHasLinks(links.isEmpty() == false);
}
if (entity.getId() == null) {
myEntityManager.persist(entity);
@ -847,7 +877,7 @@ public abstract class BaseFhirDao {
for (ResourceIndexedSearchParamString next : stringParams) {
myEntityManager.persist(next);
}
if (entity.isParamsTokenPopulated()) {
for (ResourceIndexedSearchParamToken next : paramsToken) {
myEntityManager.remove(next);
@ -883,10 +913,13 @@ public abstract class BaseFhirDao {
for (ResourceLink next : links) {
myEntityManager.persist(next);
}
myEntityManager.flush();
theResource.setId(new IdDt(entity.getResourceType(), entity.getId().toString(), Long.toString(entity.getVersion())));
myEntityManager.flush();
if (theResource!=null) {
theResource.setId(new IdDt(entity.getResourceType(), entity.getId().toString(), Long.toString(entity.getVersion())));
}
return entity;
}

View File

@ -70,6 +70,7 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.FhirTerser;
@ -479,7 +480,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
updateEntity(theResource, entity, false);
updateEntity(theResource, entity, false, false);
MethodOutcome outcome = toMethodOutcome(entity);
return outcome;
@ -617,6 +618,12 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
BaseHasResource entity = readEntity(theId);
T retVal = toResource(myResourceType, entity);
InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal);
if (deleted != null && !deleted.isEmpty()) {
throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString());
}
return retVal;
}
@ -727,7 +734,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
for (Include next : theParams.getIncludes()) {
for (IResource nextResource : retVal) {
assert myResourceType.isAssignableFrom(nextResource.getClass());
List<Object> values = t.getValues(nextResource, next.getValue());
for (Object object : values) {
if (object == null) {
@ -764,8 +771,6 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
};
}
private void loadResourcesByPid(Collection<Long> theIncludePids, List<IResource> theResourceListToPopulate) {
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
@ -952,7 +957,23 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
// });
final ResourceTable entity = readEntityLatestVersion(theId);
ResourceTable savedEntity = updateEntity(theResource, entity, true);
if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) {
throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version");
}
ResourceTable savedEntity = updateEntity(theResource, entity, true, false);
return toMethodOutcome(savedEntity);
}
@Override
public MethodOutcome delete(IdDt theId) {
final ResourceTable entity = readEntityLatestVersion(theId);
if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) {
throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version");
}
ResourceTable savedEntity = updateEntity(null, entity, true, true);
return toMethodOutcome(savedEntity);
}

View File

@ -123,7 +123,7 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
for (int i = 0; i < theResources.size(); i++) {
IResource resource = theResources.get(i);
ResourceTable table = persistedResources.get(i);
updateEntity(resource, table, table.getId() != null);
updateEntity(resource, table, table.getId() != null, false);
}
long delay = System.currentTimeMillis() - start;

View File

@ -20,6 +20,8 @@ public interface IFhirResourceDao<T extends IResource> {
MethodOutcome create(T theResource);
MethodOutcome delete(IdDt theResource);
TagList getAllResourceTags();
Class<T> getResourceType();

View File

@ -17,7 +17,11 @@ import ca.uhn.fhir.model.primitive.InstantDt;
@MappedSuperclass
public abstract class BaseHasResource {
@Column(name = "RES_ENCODING", nullable = false, length=5)
@Column(name = "RES_DELETED_AT", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date myDeleted;
@Column(name = "RES_ENCODING", nullable = false, length = 5)
@Enumerated(EnumType.STRING)
private ResourceEncodingEnum myEncoding;
@ -33,14 +37,16 @@ public abstract class BaseHasResource {
@Column(name = "RES_UPDATED", nullable = false)
private Date myUpdated;
public abstract BaseTag addTag(TagDefinition theDef);
public Date getDeleted() {
return myDeleted;
}
public ResourceEncodingEnum getEncoding() {
return myEncoding;
}
public abstract String getResourceType();
public abstract Collection<? extends BaseTag> getTags();
public abstract IdDt getIdDt();
public InstantDt getPublished() {
@ -51,12 +57,20 @@ public abstract class BaseHasResource {
return myResource;
}
public abstract String getResourceType();
public abstract Collection<? extends BaseTag> getTags();
public InstantDt getUpdated() {
return new InstantDt(myUpdated);
}
public abstract long getVersion();
public void setDeleted(Date theDate) {
myDeleted = theDate;
}
public void setEncoding(ResourceEncodingEnum theEncoding) {
myEncoding = theEncoding;
}
@ -81,6 +95,4 @@ public abstract class BaseHasResource {
myUpdated = theUpdated.getValue();
}
public abstract BaseTag addTag(TagDefinition theDef);
}

View File

@ -19,6 +19,7 @@ public class ResourceHistoryTag extends BaseTag implements Serializable {
@GeneratedValue(strategy=GenerationType.AUTO)
@Id
@Column(name = "PID")
private Long myId;
@ManyToOne()

View File

@ -28,10 +28,10 @@ import ca.uhn.fhir.rest.server.Constants;
@Inheritance(strategy = InheritanceType.JOINED)
@org.hibernate.annotations.Table(appliesTo = "HFJ_RESOURCE", indexes = { @Index(name = "IDX_RES_DATE", columnNames = { "RES_UPDATED" }) })
public class ResourceTable extends BaseHasResource implements Serializable {
static final int RESTYPE_LEN = 30;
private static final long serialVersionUID = 1L;
static final int RESTYPE_LEN = 30;
@Column(name = "SP_HAS_LINKS")
private boolean myHasLinks;
@ -256,6 +256,7 @@ public class ResourceTable extends BaseHasResource implements Serializable {
retVal.setUpdated(getUpdated());
retVal.setEncoding(getEncoding());
retVal.setResource(getResource());
retVal.setDeleted(getDeleted());
for (ResourceTag next : getTags()) {
retVal.addTag(next);

View File

@ -1,17 +1,19 @@
package ca.uhn.fhir.jpa.provider;
import java.util.Date;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
@ -19,13 +21,16 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
public class JpaResourceProvider<T extends IResource> implements IResourceProvider {
private FhirContext myContext = new FhirContext();
@Autowired(required=true)
private FhirContext myContext;
private IFhirResourceDao<T> myDao;
public JpaResourceProvider() {
@ -40,7 +45,12 @@ public class JpaResourceProvider<T extends IResource> implements IResourceProvid
public MethodOutcome create(@ResourceParam T theResource) {
return myDao.create(theResource);
}
@Delete
public MethodOutcome delete(@IdParam IdDt theResource) {
return myDao.delete(theResource);
}
public FhirContext getContext() {
return myContext;
}
@ -50,13 +60,13 @@ public class JpaResourceProvider<T extends IResource> implements IResourceProvid
}
@History
public IBundleProvider getHistoryForResourceType(@Since Date theDate) {
return myDao.history(theDate);
public IBundleProvider getHistoryForResourceInstance(@IdParam IdDt theId, @Since Date theDate) {
return myDao.history(theId.getIdPartAsLong(), theDate);
}
@History
public IBundleProvider getHistoryForResourceInstance(@IdParam IdDt theId, @Since Date theDate) {
return myDao.history(theId.getIdPartAsLong(), theDate);
public IBundleProvider getHistoryForResourceType(@Since Date theDate) {
return myDao.history(theDate);
}
@Override
@ -65,13 +75,13 @@ public class JpaResourceProvider<T extends IResource> implements IResourceProvid
}
@GetTags
public TagList getTagsForResourceType() {
return myDao.getAllResourceTags();
public TagList getTagsForResourceInstance(@IdParam IdDt theResourceId) {
return myDao.getTags(theResourceId);
}
@GetTags
public TagList getTagsForResourceInstance(@IdParam IdDt theResourceId) {
return myDao.getTags(theResourceId);
public TagList getTagsForResourceType() {
return myDao.getAllResourceTags();
}
@Read(version=true)
@ -79,6 +89,10 @@ public class JpaResourceProvider<T extends IResource> implements IResourceProvid
return myDao.read(theId);
}
public void setContext(FhirContext theContext) {
myContext = theContext;
}
@Required
public void setDao(IFhirResourceDao<T> theDao) {
myDao = theDao;
@ -89,4 +103,12 @@ public class JpaResourceProvider<T extends IResource> implements IResourceProvid
return myDao.update(theResource, theId);
}
@Validate
public MethodOutcome validate(@ResourceParam T theResource) {
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome(new OperationOutcome());
retVal.getOperationOutcome().addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Resource validates successfully");
return retVal;
}
}

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Class-Path:

View File

@ -43,6 +43,7 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
public class FhirResourceDaoTest {
@ -54,10 +55,28 @@ public class FhirResourceDaoTest {
private static IFhirResourceDao<DiagnosticReport> ourDiagnosticReportDao;
private static IFhirResourceDao<Organization> ourOrganizationDao;
private static IFhirResourceDao<Location> ourLocationDao;
private static Date ourTestStarted;
private static IFhirResourceDao<Encounter> ourEncounterDao;
@Test
public void testStoreUnversionedResources() {
Organization o1 = new Organization();
o1.getName().setValue("AAA");
IdDt o1id = ourOrganizationDao.create(o1).getId();
assertTrue(o1id.hasVersionIdPart());
Patient p1 = new Patient();
p1.addName().addFamily("AAAA");
p1.getManagingOrganization().setReference(o1id);
IdDt p1id = ourPatientDao.create(p1).getId();
p1 = ourPatientDao.read(p1id);
assertFalse(p1.getManagingOrganization().getReference().hasVersionIdPart());
assertEquals(o1id.toUnqualifiedVersionless(), p1.getManagingOrganization().getReference().toUnqualifiedVersionless());
}
@Test
public void testIdParam() {
Patient patient = new Patient();
@ -71,19 +90,19 @@ public class FhirResourceDaoTest {
Date now = new Date();
{
Patient retrieved = ourPatientDao.read(outcome.getId());
InstantDt published = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED);
InstantDt updated = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
assertTrue(published.before(now));
assertTrue(updated.before(now));
Patient retrieved = ourPatientDao.read(outcome.getId());
InstantDt published = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED);
InstantDt updated = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
assertTrue(published.before(now));
assertTrue(updated.before(now));
}
// Now search by _id
{
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.add("_id", new StringParam(outcome.getId().getIdPart()));
List<Patient> ret = toList(ourPatientDao.search(paramMap));
assertEquals(1,ret.size());
assertEquals(1, ret.size());
Patient p = ret.get(0);
assertEquals("Tester", p.getNameFirstRep().getFamilyAsSingleString());
}
@ -92,7 +111,7 @@ public class FhirResourceDaoTest {
paramMap.add("_id", new StringParam(outcome.getId().getIdPart()));
paramMap.add(Patient.SP_NAME, new StringParam("tester"));
List<Patient> ret = toList(ourPatientDao.search(paramMap));
assertEquals(1,ret.size());
assertEquals(1, ret.size());
Patient p = ret.get(0);
assertEquals("Tester", p.getNameFirstRep().getFamilyAsSingleString());
}
@ -101,7 +120,7 @@ public class FhirResourceDaoTest {
paramMap.add(Patient.SP_NAME, new StringParam("tester"));
paramMap.add("_id", new StringParam(outcome.getId().getIdPart()));
List<Patient> ret = toList(ourPatientDao.search(paramMap));
assertEquals(1,ret.size());
assertEquals(1, ret.size());
Patient p = ret.get(0);
assertEquals("Tester", p.getNameFirstRep().getFamilyAsSingleString());
}
@ -110,7 +129,7 @@ public class FhirResourceDaoTest {
paramMap.add(Patient.SP_NAME, new StringParam("tester"));
paramMap.add("_id", new StringParam("000"));
List<Patient> ret = toList(ourPatientDao.search(paramMap));
assertEquals(0,ret.size());
assertEquals(0, ret.size());
}
}
@ -166,7 +185,7 @@ public class FhirResourceDaoTest {
assertEquals(1, found.size());
// If this throws an exception, that would be an acceptable outcome as well..
found = toList(ourPatientDao.search(Patient.SP_BIRTHDATE+"AAAA", new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN, "2000-01-01")));
found = toList(ourPatientDao.search(Patient.SP_BIRTHDATE + "AAAA", new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN, "2000-01-01")));
assertEquals(0, found.size());
}
@ -221,12 +240,12 @@ public class FhirResourceDaoTest {
assertEquals(1, found.size());
assertEquals(id, found.get(0).getId().getIdPartAsLong().longValue());
// found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "M"));
// assertEquals(1, found.size());
// assertEquals(id, found.get(0).getId().asLong().longValue());
//
// found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "F"));
// assertEquals(0, found.size());
// found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "M"));
// assertEquals(1, found.size());
// assertEquals(id, found.get(0).getId().asLong().longValue());
//
// found = ourPatientDao.search(Patient.SP_GENDER, new IdentifierDt(null, "F"));
// assertEquals(0, found.size());
SearchParameterMap map = new SearchParameterMap();
map.put(Patient.SP_IDENTIFIER, new ArrayList<List<IQueryParameterType>>());
@ -235,7 +254,7 @@ public class FhirResourceDaoTest {
map.put(Patient.SP_GENDER, new ArrayList<List<IQueryParameterType>>());
map.get(Patient.SP_GENDER).add(new ArrayList<IQueryParameterType>());
map.get(Patient.SP_GENDER).get(0).add(new IdentifierDt(AdministrativeGenderCodesEnum.M.getSystem(), "M"));
found =toList( ourPatientDao.search(map));
found = toList(ourPatientDao.search(map));
assertEquals(1, found.size());
assertEquals(id, found.get(0).getId().getIdPartAsLong().longValue());
@ -246,7 +265,7 @@ public class FhirResourceDaoTest {
map.put(Patient.SP_GENDER, new ArrayList<List<IQueryParameterType>>());
map.get(Patient.SP_GENDER).add(new ArrayList<IQueryParameterType>());
map.get(Patient.SP_GENDER).get(0).add(new IdentifierDt(AdministrativeGenderCodesEnum.M.getSystem(), "F"));
found =toList( ourPatientDao.search(map));
found = toList(ourPatientDao.search(map));
assertEquals(0, found.size());
}
@ -311,7 +330,6 @@ public class FhirResourceDaoTest {
assertEquals(1, patients.size());
assertEquals(id1.getIdPart(), patients.get(0).getId().getIdPart());
params = new HashMap<String, IQueryParameterType>();
params.put(Patient.SP_FAMILY, new StringDt("testSearchNameParam01Foo"));
patients = toList(ourPatientDao.search(params));
@ -437,14 +455,77 @@ public class FhirResourceDaoTest {
assertEquals(0, patients.size());
}
@Test
public void testDelete() {
int initialHistory = ourPatientDao.history(null).size();
IdDt id1;
IdDt id2;
IdDt id2b;
{
Patient patient = new Patient();
patient.addIdentifier("urn:system", "001");
patient.addName().addFamily("Tester_testDelete").addGiven("Joe");
id1 = ourPatientDao.create(patient).getId();
}
{
Patient patient = new Patient();
patient.addIdentifier("urn:system", "002");
patient.addName().addFamily("Tester_testDelete").addGiven("John");
id2 = ourPatientDao.create(patient).getId();
}
{
Patient patient = ourPatientDao.read(id2);
patient.addIdentifier("ZZZZZZZ", "ZZZZZZZZZ");
id2b = ourPatientDao.update(patient, id2).getId();
}
ourLog.info("ID1:{} ID2:{} ID2b:{}", new Object[] { id1, id2, id2b });
Map<String, IQueryParameterType> params = new HashMap<String, IQueryParameterType>();
params.put(Patient.SP_FAMILY, new StringDt("Tester_testDelete"));
List<Patient> patients = toList(ourPatientDao.search(params));
assertEquals(2, patients.size());
ourPatientDao.delete(id1);
patients = toList(ourPatientDao.search(params));
assertEquals(1, patients.size());
ourPatientDao.read(id1);
try {
ourPatientDao.read(id1.toVersionless());
fail();
} catch (ResourceGoneException e) {
// good
}
IBundleProvider history = ourPatientDao.history(null);
assertEquals(4 + initialHistory, history.size());
List<IResource> resources = history.getResources(0, 4);
assertNotNull(resources.get(0).getResourceMetadata().get(ResourceMetadataKeyEnum.DELETED_AT));
try {
ourPatientDao.delete(id2);
fail();
} catch (InvalidRequestException e) {
// good
}
ourPatientDao.delete(id2.toVersionless());
patients = toList(ourPatientDao.search(params));
assertEquals(0, patients.size());
}
@Test
public void testSearchWithIncludes() {
{
Organization org = new Organization();
org.getName().setValue("testSearchWithIncludes_O1");
IdDt orgId = ourOrganizationDao.create(org).getId();
Patient patient = new Patient();
patient.addIdentifier("urn:system", "001");
patient.addName().addFamily("Tester_testSearchWithIncludes_P1").addGiven("Joe");
@ -473,8 +554,7 @@ public class FhirResourceDaoTest {
assertEquals(1, patients.size());
}
@Test
public void testDatePeriodParamStartOnly() {
{
@ -509,7 +589,7 @@ public class FhirResourceDaoTest {
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03",null));
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "01"));
encs = toList(ourEncounterDao.search(params));
assertEquals(0, encs.size());
@ -550,14 +630,13 @@ public class FhirResourceDaoTest {
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam( "2001-01-03",null));
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-03", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "02"));
encs = toList(ourEncounterDao.search(params));
assertEquals(0, encs.size());
}
@SuppressWarnings("unchecked")
private <T extends IResource> List<T> toList(IBundleProvider theSearch) {
return (List<T>) theSearch.getResources(0, theSearch.size());
@ -579,10 +658,10 @@ public class FhirResourceDaoTest {
List<Encounter> encs = toList(ourEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-02", "2001-01-06"));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(ourEncounterDao.search(params));
encs = toList(ourEncounterDao.search(params));
assertEquals(1, encs.size());
params = new SearchParameterMap();
@ -610,14 +689,13 @@ public class FhirResourceDaoTest {
assertEquals(0, encs.size());
params = new SearchParameterMap();
params.add(Encounter.SP_DATE, new DateRangeParam( "2001-01-05",null));
params.add(Encounter.SP_DATE, new DateRangeParam("2001-01-05", null));
params.add(Encounter.SP_IDENTIFIER, new IdentifierDt("testDatePeriodParam", "03"));
encs = toList(ourEncounterDao.search(params));
assertEquals(0, encs.size());
}
@Test
public void testSearchStringParamWithNonNormalized() {
{
@ -694,7 +772,7 @@ public class FhirResourceDaoTest {
assertFalse(outcome.getId().isEmpty());
assertEquals("1", outcome.getId().getVersionIdPart());
Date now = new Date();
Patient retrieved = ourPatientDao.read(outcome.getId());
InstantDt published = (InstantDt) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED);
@ -730,20 +808,20 @@ public class FhirResourceDaoTest {
* Get history
*/
IBundleProvider historyBundle = ourPatientDao.history(outcome.getId(),null);
IBundleProvider historyBundle = ourPatientDao.history(outcome.getId(), null);
assertEquals(2, historyBundle.size());
List<IResource> history = historyBundle.getResources(0, 2);
assertEquals("1", history.get(1).getId().getVersionIdPart());
assertEquals("2", history.get(0).getId().getVersionIdPart());
assertEquals(published, history.get(1).getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED));
assertEquals(published, history.get(1).getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED));
assertEquals(updated, history.get(1).getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED));
assertEquals("001", ((Patient)history.get(1)).getIdentifierFirstRep().getValue().getValue());
assertEquals("001", ((Patient) history.get(1)).getIdentifierFirstRep().getValue().getValue());
assertEquals(published2, history.get(0).getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED));
assertEquals(updated2, history.get(0).getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED));
assertEquals("002", ((Patient)history.get(0)).getIdentifierFirstRep().getValue().getValue());
assertEquals("002", ((Patient) history.get(0)).getIdentifierFirstRep().getValue().getValue());
}

View File

@ -22,7 +22,7 @@
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>2.1.2.RELEASE</version>
<version>${thymeleaf-version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
@ -71,8 +71,6 @@
</dependencies>
<properties>
<junit_version>4.11</junit_version>
<jetty_version>9.1.1.v20140108</jetty_version>
</properties>
<build>

View File

@ -24,7 +24,6 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.tester.RestfulServerTesterServlet;
import ca.uhn.test.jpasrv.ObservationResourceProvider;
import ca.uhn.test.jpasrv.OrganizationResourceProvider;
import ca.uhn.test.jpasrv.PatientResourceProvider;
@ -178,13 +177,8 @@ public class CompleteResourceProviderTest {
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
RestfulServerTesterServlet testerServlet = new RestfulServerTesterServlet();
String serverBase = "http://localhost:" + myPort + "/fhir/context";
testerServlet.setServerBase(serverBase);
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
ServletHolder handler = new ServletHolder();
handler.setServlet(testerServlet);
proxyHandler.addServlet(handler, "/fhir/tester/*");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer);

View File

@ -25,7 +25,6 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Questionnaire;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.tester.RestfulServerTesterServlet;
import ca.uhn.test.jpasrv.ObservationResourceProvider;
import ca.uhn.test.jpasrv.OrganizationResourceProvider;
import ca.uhn.test.jpasrv.PatientResourceProvider;
@ -89,13 +88,7 @@ public class SystemTest {
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
RestfulServerTesterServlet testerServlet = new RestfulServerTesterServlet();
String serverBase = "http://localhost:" + myPort + "/fhir/context";
testerServlet.setServerBase(serverBase);
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
ServletHolder handler = new ServletHolder();
handler.setServlet(testerServlet);
proxyHandler.addServlet(handler, "/fhir/tester/*");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer);

View File

@ -6,7 +6,6 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="target/generated-resources/tinder"/>
<classpathentry kind="src" path="target/generated-sources/tinder"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
@ -24,7 +23,7 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
@ -32,6 +31,7 @@
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>

View File

@ -1,4 +1,5 @@
myUnitTestDB/
target/
fhir/
/bin
nohup.out

View File

@ -5,6 +5,16 @@
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
@ -15,9 +25,18 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/webapp"/>
<classpathentry kind="src" path="target/m2e-wtp/web-resources"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
<attributes>
<attribute name="hide" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
<classpathentry kind="output" path=""/>
</classpath>

View File

@ -1,7 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.7
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="hapi-fhir-jpaserver-uhnfhirtest">
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-resources/tinder"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/target/generated-sources/tinder"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<dependent-module archiveName="hapi-fhir-jpaserver-base-0.4-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-jpaserver-base/hapi-fhir-jpaserver-base">
<dependency-type>uses</dependency-type>
</dependent-module>
<dependent-module archiveName="hapi-fhir-base-0.4-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
<dependency-type>uses</dependency-type>
</dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-tester-overlay?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependency-type>consumes</dependency-type>
</dependent-module>
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&amp;excludes=META-INF/MANIFEST.MF">
<dependency-type>consumes</dependency-type>
</dependent-module>
<property name="context-root" value="hapi-fhir-jpaserver"/>
<property name="java-output-path" value="/hapi-fhir-jpaserver-uhnfhirtest/target/classes"/>
</wb-module>
</project-modules>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<fixed facet="wst.jsdt.web"/>
<installed facet="java" version="1.6"/>
<installed facet="jst.web" version="3.0"/>
<installed facet="wst.jsdt.web" version="1.0"/>
</faceted-project>

View File

@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.baseBrowserLibrary

View File

@ -0,0 +1,2 @@
disabled=06target
eclipse.preferences.version=1

View File

@ -18,29 +18,123 @@
<artifactId>hapi-fhir-jpaserver-base</artifactId>
<version>0.4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-testpage-overlay</artifactId>
<version>0.4-SNAPSHOT</version>
<type>war</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-test</artifactId>
<version>0.4-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring_version}</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby_version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbynet</artifactId>
<version>${derby_version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>${derby_version}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>2.1.2.RELEASE</version>
<version>${thymeleaf-version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
<version>${logback-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j_version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- TEST DEPS -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>${jetty_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${jetty_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${jetty_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
<build>
@ -131,15 +225,29 @@
<baseResourceName>specimen</baseResourceName>
<baseResourceName>substance</baseResourceName>
<baseResourceName>supply</baseResourceName>
<!-- <baseResourceName>test</baseResourceName> -->
<!-- <baseResourceName>test</baseResourceName> -->
<baseResourceName>user</baseResourceName>
<baseResourceName>valueset</baseResourceName>
</baseResourceNames>
<buildDatatypes>true</buildDatatypes>
<targetResourceDirectory>${project.build.directory}/hapi-fhir-jpaserver/WEB-INF</targetResourceDirectory>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders.xml</targetResourceSpringBeansFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-testpage-overlay</artifactId>
</overlay>
</overlays>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>

View File

@ -0,0 +1,30 @@
package ca.uhn.fhirtest;
import java.sql.DriverManager;
import org.apache.derby.drda.NetworkServerControl;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class DerbyNetworkServer implements InitializingBean, DisposableBean {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DerbyNetworkServer.class);
private NetworkServerControl server;
@Override
public void destroy() throws Exception {
server.shutdown();
try {
ourLog.info("Shutting down derby");
// DriverManager.getConnection("jdbc:derby:directory:" + System.getProperty("fhir.db.location") + ";shutdown=true");
} catch (Exception e) {
ourLog.info("Failed to create database: {}", e.getMessage());
}
}
@Override
public void afterPropertiesSet() throws Exception {
server = new NetworkServerControl();
server.start (null);
}
}

View File

@ -0,0 +1,73 @@
package ca.uhn.fhirtest;
import java.io.IOException;
import java.util.Properties;
import org.hsqldb.Server;
import org.hsqldb.persist.HsqlProperties;
import org.hsqldb.server.ServerAcl.AclFormatException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.SmartLifecycle;
public class HsqldbServer implements SmartLifecycle {
private final Logger logger = LoggerFactory.getLogger(HsqldbServer.class);
private HsqlProperties properties;
private Server server;
private boolean running = false;
public HsqldbServer(Properties props) {
properties = new HsqlProperties(props);
}
@Override
public boolean isRunning() {
if (server != null)
server.checkRunning(running);
return running;
}
@Override
public void start() {
if (server == null) {
logger.info("Starting HSQL server...");
server = new Server();
try {
server.setProperties(properties);
server.start();
running = true;
} catch (AclFormatException afe) {
logger.error("Error starting HSQL server.", afe);
throw new Error(afe);
} catch (IOException e) {
logger.error("Error starting HSQL server.", e);
throw new Error(e);
}
}
}
@Override
public void stop() {
logger.info("Stopping HSQL server...");
if (server != null) {
server.stop();
running = false;
}
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable runnable) {
stop();
runnable.run();
}
}

View File

@ -3,7 +3,11 @@ package ca.uhn.fhirtest;
import java.sql.DriverManager;
import java.util.Collection;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProvider;
@ -16,21 +20,30 @@ public class TestRestfulServer extends RestfulServer {
private static final long serialVersionUID = 1L;
private ClassPathXmlApplicationContext myAppCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestRestfulServer.class);
private ApplicationContext myAppCtx;
@Override
protected void initialize() {
super.initialize();
try {
ourLog.info("Creating database");
DriverManager.getConnection("jdbc:derby:directory:" + System.getProperty("fhir.db.location") + ";create=true");
} catch (Exception e) {
ourLog.error("Failed to create database: {}",e);
}
// try {
// ourLog.info("Creating database");
// DriverManager.getConnection("jdbc:derby:directory:" + System.getProperty("fhir.db.location") + ";create=true");
// } catch (Exception e) {
// ourLog.error("Failed to create database: {}",e);
// }
myAppCtx = new ClassPathXmlApplicationContext("fhir-spring-uhnfhirtest-config.xml", "hapi-jpaserver-springbeans.xml");
// myAppCtx = new ClassPathXmlApplicationContext("fhir-spring-uhnfhirtest-config.xml", "hapi-jpaserver-springbeans.xml");
// myAppCtx = new FileSystemXmlApplicationContext(
// "WEB-INF/hapi-fhir-server-database-config.xml",
// "WEB-INF/hapi-fhir-server-config.xml"
// );
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
Collection<IResourceProvider> beans = myAppCtx.getBeansOfType(IResourceProvider.class).values();
for (IResourceProvider nextResourceProvider : beans) {
@ -55,14 +68,14 @@ public class TestRestfulServer extends RestfulServer {
public void destroy() {
super.destroy();
myAppCtx.close();
try {
ourLog.info("Shutting down derby");
DriverManager.getConnection("jdbc:derby:directory:" + System.getProperty("fhir.db.location") + ";shutdown=true");
} catch (Exception e) {
ourLog.info("Failed to create database: {}",e.getMessage());
}
// myAppCtx.close();
//
// try {
// ourLog.info("Shutting down derby");
// DriverManager.getConnection("jdbc:derby:directory:" + System.getProperty("fhir.db.location") + ";shutdown=true");
// } catch (Exception e) {
// ourLog.info("Failed to create database: {}",e.getMessage());
// }
}
}

View File

@ -1,18 +0,0 @@
package ca.uhn.fhirtest;
import ca.uhn.fhir.rest.server.tester.RestfulTesterServlet;
public class TesterServlet extends RestfulTesterServlet {
private static final long serialVersionUID = 1L;
public TesterServlet() {
String baseUrl = System.getProperty("fhir.baseurl");
addServerBase("fhirtest", "UHN/HAPI Test Server", baseUrl);
addServerBase("hi", "Health Intersections Ref Server", "http://fhir.healthintersections.com.au/open");
addServerBase("furore", "Spark - Furore Ref Server", "http://spark.furore.com/fhir");
addServerBase("blaze", "Blaze (Orion Health)", "https://his-medicomp-gateway.orionhealth.com/blaze/fhir");
}
}

View File

@ -0,0 +1,16 @@
<configuration scan="true" scanPeriod="30 seconds">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT" />
</root>
</configuration>

Some files were not shown because too many files have changed in this diff Show More