Move searches with required parameters to unnamed queries

This commit is contained in:
jamesagnew 2014-07-07 09:23:23 -04:00
parent add96f2124
commit cb2b54cb09
30 changed files with 714 additions and 398 deletions

View File

@ -97,6 +97,9 @@ public class ExtensionDt extends BaseIdentifiableElement implements ICompositeDa
* If the value of this extension is not a primitive datatype
*/
public IPrimitiveDatatype<?> getValueAsPrimitive() {
if (!(getValue() instanceof IPrimitiveDatatype)) {
throw new ClassCastException("Extension with URL["+myUrl+"] can not be cast to primitive type, type is: "+ getClass().getCanonicalName());
}
return (IPrimitiveDatatype<?>) getValue();
}

View File

@ -26,7 +26,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER})
@Target(value= {ElementType.FIELD, ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD})
public @interface Description {
/**

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
@ -32,6 +34,7 @@ import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.rest.param.BaseQueryParameter;
@ -47,17 +50,34 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
private Class<?> myDeclaredResourceType;
private Class<? extends IResource> myDeclaredResourceType;
private String myQueryName;
private String myDescription;
@SuppressWarnings("unchecked")
public SearchMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, String theQueryName, FhirContext theContext, Object theProvider) {
super(theReturnResourceType, theMethod, theContext, theProvider);
this.myQueryName = StringUtils.defaultIfBlank(theQueryName, null);
this.myDeclaredResourceType = theMethod.getReturnType();
this.myDeclaredResourceType = (Class<? extends IResource>) theMethod.getReturnType();
Description desc = theMethod.getAnnotation(Description.class);
if (desc != null) {
if (isNotBlank(desc.formalDefinition())) {
myDescription = StringUtils.defaultIfBlank(desc.formalDefinition(), null);
} else {
myDescription = StringUtils.defaultIfBlank(desc.shortDefinition(), null);
}
}
}
public Class<?> getDeclaredResourceType() {
return myDeclaredResourceType.getClass();
public String getDescription() {
return myDescription;
}
public Class<? extends IResource> getDeclaredResourceType() {
return myDeclaredResourceType;
}
@Override
@ -136,10 +156,10 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return false;
}
// This is used to track all the parameters so we can reject queries that
// This is used to track all the parameters so we can reject queries that
// have additional params we don't understand
Set<String> methodParamsTemp = new HashSet<String>();
Set<String> unqualifiedNames = theRequest.getUnqualifiedToQualifiedNames().keySet();
Set<String> qualifiedParamNames = theRequest.getParameters().keySet();
for (int i = 0; i < this.getParameters().size(); i++) {
@ -196,7 +216,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return true;
}
public void setResourceType(Class<?> resourceType) {
public void setResourceType(Class<? extends IResource> resourceType) {
this.myDeclaredResourceType = resourceType;
}

View File

@ -523,7 +523,15 @@ public class RestfulServer extends HttpServlet {
resourceMethod = resourceBinding.getMethod(r);
}
if (null == resourceMethod) {
throw new InvalidRequestException("No resource method available for the supplied parameters " + params);
StringBuilder b = new StringBuilder();
b.append("No resource method available for ");
b.append(theRequestType.name());
b.append(" operation[");
b.append(requestPath);
b.append("]");
b.append(" with parameters ");
b.append(params.keySet());
throw new InvalidRequestException(b.toString());
}
resourceMethod.invokeServer(this, r, theResponse);

View File

@ -35,8 +35,10 @@ import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource;
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.RestQuery;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceOperation;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceSearchParam;
@ -105,8 +107,9 @@ public class ServerConformanceProvider {
resource.getProfile().setId(new IdDt(def.getResourceProfile()));
TreeSet<String> includes = new TreeSet<String>();
Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String, Conformance.RestResourceSearchParam>();
// Map<String, Conformance.RestResourceSearchParam> nameToSearchParam = new HashMap<String,
// Conformance.RestResourceSearchParam>();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
RestfulOperationTypeEnum resOp = nextMethodBinding.getResourceOperationType();
if (resOp != null) {
@ -127,7 +130,7 @@ public class ServerConformanceProvider {
if (nextMethodBinding instanceof SearchMethodBinding) {
SearchMethodBinding searchMethodBinding = (SearchMethodBinding) nextMethodBinding;
includes.addAll(searchMethodBinding.getIncludes());
List<IParameter> params = searchMethodBinding.getParameters();
List<SearchParameter> searchParameters = new ArrayList<SearchParameter>();
for (IParameter nextParameter : params) {
@ -152,8 +155,13 @@ public class ServerConformanceProvider {
}
boolean allOptional = searchParameters.get(0).isRequired() == false;
RestResourceSearchParam searchParam = null;
ExtensionDt searchParamChain = null;
RestQuery query = null;
if (!allOptional) {
query = rest.addQuery();
query.getDocumentation().setValue(searchMethodBinding.getDescription());
query.addUndeclaredExtension(false, ExtensionConstants.QUERY_RETURN_TYPE, new CodeDt(resourceName));
}
for (SearchParameter nextParameter : searchParameters) {
String nextParamName = nextParameter.getName();
@ -175,56 +183,21 @@ public class ServerConformanceProvider {
}
}
if (searchParam == null || allOptional) {
if (!nameToSearchParam.containsKey(nextParameter.getName())) {
RestResourceSearchParam param = resource.addSearchParam();
param.setName(nextParamName);
if (StringUtils.isNotBlank(chain)) {
param.addChain(chain);
}
param.setDocumentation(nextParamDescription);
param.setType(nextParameter.getParamType());
searchParam = param;
} else {
searchParam = nameToSearchParam.get(nextParameter.getName());
}
if (searchParam != null) {
searchParam.setType(nextParameter.getParamType());
}
RestResourceSearchParam param;
if (query == null) {
param = resource.addSearchParam();
} else {
if (searchParamChain == null) {
searchParamChain = searchParam.addUndeclaredExtension(false, ExtensionConstants.CONF_ADDITIONAL_PARAM);
} else {
searchParamChain = searchParamChain.addUndeclaredExtension(false, ExtensionConstants.CONF_ADDITIONAL_PARAM);
}
ExtensionDt ext = new ExtensionDt();
ext.setUrl(ExtensionConstants.CONF_ADDITIONAL_PARAM_NAME);
ext.setValue(new StringDt(nextParamName));
searchParamChain.getUndeclaredExtensions().add(ext);
ext = new ExtensionDt();
ext.setUrl(ExtensionConstants.CONF_ADDITIONAL_PARAM_DESCRIPTION);
ext.setValue(new StringDt(nextParamDescription));
searchParamChain.getUndeclaredExtensions().add(ext);
ext = new ExtensionDt();
ext.setUrl(ExtensionConstants.CONF_ADDITIONAL_PARAM_TYPE);
if (nextParameter.getParamType() != null) {
ext.setValue(new CodeDt(nextParameter.getParamType().getCode()));
}
searchParamChain.getUndeclaredExtensions().add(ext);
ext = new ExtensionDt();
ext.setUrl(ExtensionConstants.CONF_ADDITIONAL_PARAM_REQUIRED);
ext.setValue(new BooleanDt(nextParameter.isRequired()));
searchParamChain.getUndeclaredExtensions().add(ext);
param = query.addParameter();
param.addUndeclaredExtension(false, ExtensionConstants.PARAM_IS_REQUIRED, new BooleanDt(nextParameter.isRequired()));
}
param.setName(nextParamName);
if (StringUtils.isNotBlank(chain)) {
param.addChain(chain);
}
param.setDocumentation(nextParamDescription);
param.setType(nextParameter.getParamType());
}
}
@ -251,7 +224,7 @@ public class ServerConformanceProvider {
for (String nextInclude : includes) {
resource.addSearchInclude(nextInclude);
}
}
myConformance = retVal;
@ -259,7 +232,8 @@ public class ServerConformanceProvider {
}
/**
* Sets the cache property (default is true). If set to true, the same response will be returned for each invocation.
* Sets the cache property (default is true). If set to true, the same response will be returned for each
* invocation.
*/
public void setCache(boolean theCache) {
myCache = theCache;

View File

@ -21,6 +21,9 @@ package ca.uhn.fhir.util;
*/
public class ExtensionConstants {
public static final String PARAM_IS_REQUIRED = "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#paramIsRequired";
public static final String QUERY_RETURN_TYPE = "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#queryReturnType";
public static final String CONF_ADDITIONAL_PARAM = "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParam";
@ -31,5 +34,5 @@ public class ExtensionConstants {
public static final String CONF_ADDITIONAL_PARAM_TYPE = "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParamType";
public static final String CONF_ADDITIONAL_PARAM_REQUIRED = "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParamRequired";
}

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.lang.annotation.Documented;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@ -14,6 +15,8 @@ import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
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.RestQuery;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceSearchParam;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
@ -110,14 +113,15 @@ public class ServerConformanceProviderTest {
rs.init(null);
Conformance conformance = sc.getServerConformance();
String conf = new FhirContext().newJsonParser().setPrettyPrint(true).encodeResourceToString(conformance);
String conf = new FhirContext().newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
RestResource res = conformance.getRestFirstRep().getResourceFirstRep();
Rest rest = conformance.getRestFirstRep();
RestResource res = rest.getResourceFirstRep();
assertEquals("DiagnosticReport", res.getType().getValueAsString());
RestResourceSearchParam p0 = res.getSearchParam().get(0);
assertEquals("subject", p0.getName().getValue());
RestQuery p0 = rest.getQueryFirstRep();
assertEquals("subject", p0.getParameterFirstRep().getName().getValue());
assertEquals(1,res.getSearchInclude().size());
assertEquals("DiagnosticReport.result", res.getSearchIncludeFirstRep().getValue());
@ -160,6 +164,7 @@ public class ServerConformanceProviderTest {
public static class ProviderWithRequiredAndOptional {
@Description(shortDefinition="This is a search for stuff!")
@Search
public List<DiagnosticReport> findDiagnosticReportsByPatient (
@RequiredParam(name=DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) IdentifierDt thePatientId,

View File

@ -375,8 +375,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
String likeExpression = normalizeString(string);
likeExpression = likeExpression.replace("%", "[%]") + "%";
likeExpression = likeExpression.replace("%", "[%]") + "%";
Predicate singleCode = builder.like(from.get("myValueNormalized").as(String.class), likeExpression);
if (params instanceof StringParam && ((StringParam) params).isExact()) {
Predicate exactCode = builder.equal(from.get("myValueExact"), string);
@ -461,6 +461,32 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
return new HashSet<Long>(q.getResultList());
}
private Set<Long> addPredicateId(String theParamName, Set<Long> theExistingPids, Set<Long> thePids) {
if (thePids == null || thePids.isEmpty()) {
return Collections.emptySet();
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
cq.select(from.get("myId").as(Long.class));
Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName);
Predicate idPrecidate = from.get("myId").in(thePids);
cq.where(builder.and(typePredicate, idPrecidate));
TypedQuery<Long> q = myEntityManager.createQuery(cq);
HashSet<Long> found = new HashSet<Long>(q.getResultList());
if (!theExistingPids.isEmpty()) {
theExistingPids.retainAll(found);
}
return found;
}
@Override
public void addTag(IdDt theId, String theScheme, String theTerm, String theLabel) {
BaseHasResource entity = readEntity(theId);
@ -781,7 +807,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
if (theIncludePids.isEmpty()) {
return;
}
CriteriaBuilder builder = myEntityManager.getCriteriaBuilder();
CriteriaQuery<ResourceTable> cq = builder.createQuery(ResourceTable.class);
Root<ResourceTable> from = cq.from(ResourceTable.class);
@ -843,7 +869,8 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
} else {
for (IQueryParameterType next : nextValue) {
String value = next.getValueAsQueryToken();
long valueLong = Long.parseLong(value);
IdDt valueId = new IdDt(value);
long valueLong = valueId.getIdPartAsLong();
joinPids.add(valueLong);
}
if (joinPids.isEmpty()) {
@ -851,6 +878,11 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
}
pids = addPredicateId(nextParamName, pids, joinPids);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
if (pids.isEmpty()) {
pids.addAll(joinPids);
} else {
@ -858,47 +890,48 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
}
}
}
} else {
RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName);
if (nextParamDef != null) {
if (nextParamDef.getParamType() == SearchParamTypeEnum.TOKEN) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateToken(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName);
if (nextParamDef != null) {
if (nextParamDef.getParamType() == SearchParamTypeEnum.TOKEN) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateToken(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
}
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.STRING) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateString(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.STRING) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateString(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
}
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.QUANTITY) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateQuantity(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.QUANTITY) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateQuantity(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
}
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.DATE) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateDate(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.DATE) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateDate(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
}
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.REFERENCE) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateReference(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
} else if (nextParamDef.getParamType() == SearchParamTypeEnum.REFERENCE) {
for (List<IQueryParameterType> nextAnd : nextParamEntry.getValue()) {
pids = addPredicateReference(nextParamName, pids, nextAnd);
if (pids.isEmpty()) {
return new HashSet<Long>();
}
}
} else {
throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType());
}
} else {
throw new IllegalArgumentException("Don't know how to handle parameter of type: " + nextParamDef.getParamType());
}
}
}

View File

@ -447,6 +447,37 @@ public class FhirResourceDaoTest {
assertEquals(0, patients.size());
}
@Test
public void testSearchByIdParam() {
IdDt id1;
{
Patient patient = new Patient();
patient.addIdentifier("urn:system", "001");
id1 = ourPatientDao.create(patient).getId();
}
IdDt id2;
{
Organization patient = new Organization();
patient.addIdentifier("urn:system", "001");
id2 = ourOrganizationDao.create(patient).getId();
}
Map<String, IQueryParameterType> params = new HashMap<String, IQueryParameterType>();
params.put("_id", new StringDt(id1.getIdPart()));
assertEquals(1, toList(ourPatientDao.search(params)).size());
params.put("_id", new StringDt("9999999999999999"));
assertEquals(0, toList(ourPatientDao.search(params)).size());
params.put("_id", new StringDt(id2.getIdPart()));
assertEquals(0, toList(ourPatientDao.search(params)).size());
}
@Test
public void testSearchResourceLinkWithChain() {

View File

@ -30,7 +30,8 @@
<!-- <property name="url" value="jdbc:hsqldb:hsql://localhost/uhnfhirdb"/>-->
<!-- <property name="url" value="jdbc:derby:directory:#{systemproperties['fhir.db.location']};create=true" /> -->
<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
<property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" />
<property name="url" value="jdbc:derby://localhost:1527/#{systemProperties['fhir.db.location']};create=true" />
<!-- <property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" /> -->
<!--<property name="url" value="jdbc:derby://localhost:1527#{systemProperties['fhir.db.location']};create=true" />-->
<property name="username" value="SA"/>
<property name="password" value="SA"/>

View File

@ -1,56 +0,0 @@
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!--
<bean class="ca.uhn.fhirtest.HsqldbServer" id="dbServer" init-method="start">
<constructor-arg>
<value>
server.database.0=file:#{systemProperties['fhir.db.location']}/hsql-fhir-db
server.dbname.0=uhnfhirdb
server.remote_open=true
hsqldb.reconfig_logging=false
hsqldb.default_table_type=cached
</value>
</constructor-arg>
</bean>
-->
<bean id="dbServer" class="ca.uhn.fhirtest.DerbyNetworkServer">
</bean>
<bean depends-on="dbServer" id="myPersistenceDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- ;create=true /opt/glassfish/glassfish4/glassfish/nodes/localhost-domain1/fhirtest/fhirdb -->
<!-- <property name="url" value="jdbc:hsqldb:hsql://localhost/uhnfhirdb"/>-->
<!-- <property name="url" value="jdbc:derby:directory:#{systemproperties['fhir.db.location']};create=true" /> -->
<property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
<<<<<<< HEAD
<property name="url" value="jdbc:derby://localhost:1527//opt/glassfish/fhirtest/fhirtest;create=true" />
=======
<property name="url" value="jdbc:derby://localhost:1527#{systemProperties['fhir.db.location']};create=true" />
>>>>>>> ca0929df07b8d435a3b756f89998f68e2aae79fc
<property name="username" value="SA"/>
<property name="password" value="SA"/>
</bean>
<bean depends-on="dbServer" id="myEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="myPersistenceDataSource" />
<property name="persistenceXmlLocation" value="classpath:META-INF/fhirtest_persistence.xml" />
<property name="persistenceUnitName" value="FHIR_UT" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.DerbyTenSevenDialect" />
<!-- <property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" />-->
</bean>
</property>
</bean>
</beans>

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head th:include="tmpl-head :: head">
<title>About This Server</title>
</head>
<body>
<form action="" method="get" id="outerForm">
<div th:replace="tmpl-navbar-top :: top" ></div>
<div class="container-fluid">
<div class="row">
<div th:replace="tmpl-navbar-left :: left" ></div>
<div class="col-sm-9 col-sm-offset-3 col-md-9 col-md-offset-3 main">
<div th:replace="tmpl-banner :: banner"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">About This Server</h3>
</div>
<div class="panel-body">
<div class="pull-right">
<object data="img/fhirtest-architecture.svg" width="383" height="369" type="image/svg+xml" />
</div>
<p>
This server provides a nearly complete implementation of the FHIR Specification
using a 100% open source software stack. It is hosted by University Health Network.
</p>
<p>
The architecture in use here is shown in the image on the right. This server is built
from a number of modules of the
<a href="https://github.com/jamesagnew/hapi-fhir/">HAPI FHIR</a>
project, which is a 100% open-source (Apache 2.0 Licensed) Java based
implementation of the FHIR specification.
</p>
<p>
</p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Data On This Server</h3>
</div>
<div class="panel-body">
<p>
This server is regularly loaded with a standard set of test data sourced
from UHN's own testing environment. Do not use this server to store any data
that you will need later, as we will be regularly resetting it.
</p>
<p>
This is not a production server and it provides no privacy. Do not store any
confidential data here.
</p>
</div>
</div>
</div>
</div>
</div>
<div th:replace="tmpl-footer :: footer" ></div>
</form>
</body>
</html>

View File

@ -1,75 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<div th:fragment="banner" class="well">
<th:block th:if="${serverEntry.key} == 'home'">
<p>
This is the home for the FHIR test server operated by
<a href="http://uhn.ca">University Health Network</a>. This server
(and the testing application you are currently using to access it)
is entirely built using
<a href="https://github.com/jamesagnew/hapi-fhir">HAPI-FHIR</a>,
a 100% open-source Java implementation of the
<a href="http://hl7.org/implement/standards/fhir/">FHIR specification</a>.
</p>
<p>
Here are some things you might wish to try:
</p>
<ul>
<li>
View a
<a href="http://fhirtest.uhn.ca/search?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient&amp;param.0.type=string&amp;param.0.name=_id&amp;param.0.0=&amp;resource-search-limit=">list of patients</a>
on this server.
</li>
<li>
Construct a
<a href="http://fhirtest.uhn.ca/resource?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient">search query</a>
on this server.
</li>
<li>
Access a
<a href="http://fhirtest.uhn.ca/home?serverId=furore">different server</a>
(use the <b>Server</b> menu at the top of the page to see a list of public FHIR servers)
</li>
</ul>
</th:block>
<th:block th:if="${serverEntry.key} != 'home'">
<p>
You are accessing the public FHIR server
<b th:text="${baseName}"/>. This server is hosted elsewhere on the internet
but is being accessed using
</p>
</th:block>
<p>
<b style="color: red;">
<span class="glyphicon glyphicon-warning-sign/>
This is not a production server!
</b>
Do not store any information here that contains personal health information
or any other confidential information. This server will be regularly purged
and reloaded with fixed test data.
</p>
<<<<<<< HEAD
<p>
Here are some things you might wish to try:
</p>
<ul>
<li>
View a
<a href="http://fhirtest.uhn.ca/search?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient&amp;param.0.type=string&amp;param.0.name=_id&amp;param.0.0=&amp;resource-search-limit=">list of patients</a>
on this server.
</li>
<li>
Construct a
<a href="http://fhirtest.uhn.ca/resource?serverId=home&amp;encoding=json&amp;pretty=true&amp;resource=Patient">search query</a>
on this server.
</li>
<li>
Access a
<a href="http://fhirtest.uhn.ca/home?serverId=furore">different server</a>
(use the <b>Server</b> menu at the top of the page to see a list of public FHIR servers)
</li>
</ul>
=======
>>>>>>> ca0929df07b8d435a3b756f89998f68e2aae79fc
</div>
</html>

View File

@ -0,0 +1,115 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="383px" height="369px" version="1.1">
<defs />
<g transform="translate(0.5,0.5)">
<rect x="1" y="177" width="170" height="50" rx="8" ry="8" fill="#ffffff" stroke="#000000" pointer-events="none" />
<g transform="translate(3,187)">
<switch>
<foreignObject pointer-events="all" width="166" height="30" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml"
style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 166px; white-space: normal; text-align: center;">
<a href="https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-base">hapi-fhir-base</a>
<br />
(RESTful Server)
</div>
</foreignObject>
<text x="83" y="21" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<rect x="1" y="247" width="170" height="50" rx="8" ry="8" fill="#ffffff" stroke="#000000" pointer-events="none" />
<g transform="translate(3,250)">
<switch>
<foreignObject pointer-events="all" width="166" height="45" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml"
style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 166px; white-space: normal; text-align: center;">
<a href="https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-jpaserver-base">hapi-fhir-jpaserver-base</a>
<br />
Hibernate + Spring
<br />
(Database Abstraction)
</div>
</foreignObject>
<text x="83" y="29" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<rect x="1" y="317" width="170" height="50" rx="8" ry="8" fill="#ffffff" stroke="#000000" pointer-events="none" />
<g transform="translate(3,327)">
<switch>
<foreignObject pointer-events="all" width="166" height="30" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml"
style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 166px; white-space: normal; text-align: center;">
Apache Derby
<br />
(Database)
</div>
</foreignObject>
<text x="83" y="21" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<rect x="211" y="177" width="170" height="50" rx="8" ry="8" fill="#ffffff" stroke="#000000" pointer-events="none" />
<g transform="translate(213,179)">
<switch>
<foreignObject pointer-events="all" width="166" height="45" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml"
style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 166px; white-space: normal; text-align: center;">
<a href="https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-testpage-overlay">hapi-fhir-testpage-overlay</a>
<br />
Thymeleaf + Spring
<br />
(Testing Application)
</div>
</foreignObject>
<text x="83" y="21" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 86 247 Q 86 247 86 233" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 86 228 L 90 235 L 86 233 L 83 235 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 86 317 Q 86 317 86 303" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 86 298 L 90 305 L 86 303 L 83 305 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 171 202 Q 171 202 205 202" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 210 202 L 203 206 L 205 202 L 203 199 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<image x="111" y="32" width="40" height="40" xlink:href="https://cdn1.iconfinder.com/data/icons/professional-toolbar-icons-2/64/Web_server_internet_vector.png"
preserveAspectRatio="none" pointer-events="none" />
<g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
<text x="131" y="12">FHIR Clients and</text>
<text x="131" y="26">Applications</text>
</g>
<image x="221" y="32" width="40" height="40" xlink:href="https://cdn2.iconfinder.com/data/icons/crystalproject/128x128/filesystems/Globe2.png" preserveAspectRatio="none"
pointer-events="none" />
<g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
<text x="241" y="26">Browsers</text>
</g>
<path d="M 86 177 L 86 132 Q 86 122 96 122 L 111 122 Q 121 122 121 112 L 121 78" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 121 73 L 125 80 L 121 78 L 118 80 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 296 177 L 296 132 Q 296 122 286 122 L 251 122 Q 241 122 241 112 L 241 78" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 241 73 L 245 80 L 241 78 L 238 80 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<rect x="71" y="97" width="240" height="50" rx="8" ry="8" fill-opacity="0.35" fill="#ababab" stroke="#000000" stroke-opacity="0.35" pointer-events="none" />
<g transform="translate(73,107)">
<switch>
<foreignObject pointer-events="all" width="236" height="30" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml"
style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.26; vertical-align: top; width: 236px; white-space: normal; font-weight: bold; text-align: center;">
Apache
<br />
HTTPd
</div>
</foreignObject>
<text x="118" y="21" fill="#000000" text-anchor="middle" font-size="12px" font-family="Helvetica" font-weight="bold">[Not supported by viewer]</text>
</switch>
</g>
<image x="276" y="292" width="40" height="40" xlink:href="https://cdn2.iconfinder.com/data/icons/seo-web-optomization-ultimate-set/512/web_hosting-128.png"
preserveAspectRatio="none" pointer-events="none" />
<g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
<text x="296" y="350">Other Public</text>
<text x="296" y="364">FHIR Servers</text>
</g>
<path d="M 296 292 L 296 233" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 296 228 L 300 235 L 296 233 L 293 235 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<image x="316" y="33" width="35" height="37" xlink:href="https://cdn2.iconfinder.com/data/icons/ios-7-icons/50/user_male4-128.png" preserveAspectRatio="none"
pointer-events="none" />
<g fill="#000000" font-family="Helvetica" text-anchor="middle" font-size="12px">
<text x="334" y="27">(You are here)</text>
</g>
<path d="M 316 52 L 267 52" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
<path d="M 262 52 L 269 48 L 267 52 L 269 55 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -22,10 +22,13 @@ public class UhnFhirTestApp {
public static void main(String[] args) throws Exception {
// new File("target/testdb").mkdirs();
System.setProperty("fhir.db.location", "/target/testdb");
int myPort = 8888;
String base = "http://localhost:" + myPort + "/base";
// new File("target/testdb").mkdirs();
System.setProperty("fhir.db.location", "./target/testdb");
System.setProperty("fhir.baseurl", base);
Server server = new Server(myPort);
WebAppContext root = new WebAppContext();
@ -40,7 +43,6 @@ public class UhnFhirTestApp {
server.start();
String base = "http://localhost:" + myPort + "/base";
// base = "http://fhir.healthintersections.com.au/open";
// base = "http://spark.furore.com/fhir";

View File

@ -36,6 +36,7 @@ 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.RestQuery;
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.DecimalDt;
@ -51,6 +52,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.to.model.HomeRequest;
import ca.uhn.fhir.to.model.ResourceRequest;
import ca.uhn.fhir.to.model.TransactionRequest;
import ca.uhn.fhir.util.ExtensionConstants;
@org.springframework.stereotype.Controller()
public class Controller {
@ -68,6 +70,18 @@ public class Controller {
@Autowired
private TemplateEngine myTemplateEngine;
@RequestMapping(value = { "/about" })
public String about( final HomeRequest theRequest, final ModelMap theModel) {
addCommonParams(theRequest, theModel);
GenericClient client = theRequest.newClient(myCtx, myConfig);
loadAndAddConformance(theRequest, theModel, client);
theModel.put("notHome", true);
theModel.put("extraBreadcrumb", "About");
return "about";
}
@RequestMapping(value = { "/transaction" })
public String actionTransaction(final TransactionRequest theRequest, final BindingResult theBindingResult, final ModelMap theModel) {
addCommonParams(theRequest, theModel);
@ -170,36 +184,41 @@ public class Controller {
loadAndAddConformance(theRequest, theModel, client);
Class<? extends IResource> resType = null;
long start = System.currentTimeMillis();
if (isNotBlank(theReq.getParameter(PARAM_RESOURCE))) {
RuntimeResourceDefinition def;
try {
def = getResourceType(theReq);
} catch (ServletException e) {
theModel.put("errorMsg", e.toString());
return "resource";
}
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();
}
long delay = System.currentTimeMillis() - start;
ResultType returnsResource = ResultType.TAGLIST;
String outcomeDescription = "Tag List";
long start = System.currentTimeMillis();
try {
if (isNotBlank(theReq.getParameter(PARAM_RESOURCE))) {
RuntimeResourceDefinition def;
try {
def = getResourceType(theReq);
} catch (ServletException e) {
theModel.put("errorMsg", e.toString());
return "resource";
}
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();
}
} catch (Exception e) {
returnsResource = ResultType.NONE;
ourLog.warn("Failed to invoke server", e);
}
long delay = System.currentTimeMillis() - start;
processAndAddLastClientInvocation(client, returnsResource, theModel, delay, outcomeDescription);
return "result";
@ -311,6 +330,8 @@ public class Controller {
RuntimeResourceDefinition def = myCtx.getResourceDefinition(theRequest.getResource());
TreeSet<String> includes = new TreeSet<String>();
List<RestQuery> queries = new ArrayList<Conformance.RestQuery>();
boolean haveSearchParams = false;
for (Rest nextRest : conformance.getRest()) {
for (RestResource nextRes : nextRest.getResource()) {
if (nextRes.getType().getValue().equals(resourceName)) {
@ -319,10 +340,25 @@ public class Controller {
includes.add(next.getValue());
}
}
if (nextRes.getSearchParam().size() > 0) {
haveSearchParams = true;
}
}
}
for (RestQuery nextQuery : nextRest.getQuery()) {
List<ExtensionDt> returnTypeExt = nextQuery.getUndeclaredExtensionsByUrl(ExtensionConstants.QUERY_RETURN_TYPE);
if (returnTypeExt != null) {
for (ExtensionDt nextExt : returnTypeExt) {
if (resourceName.equals(nextExt.getValueAsPrimitive().getValueAsString())) {
queries.add(nextQuery);
}
}
}
}
}
theModel.put("includes", includes);
theModel.put("queries", queries);
theModel.put("haveSearchParams", haveSearchParams);
if (isNotBlank(theRequest.getUpdateId())) {
String updateId = theRequest.getUpdateId();
@ -510,7 +546,7 @@ public class Controller {
}
private String preProcessMessageBody(String theBody) {
if(theBody==null) {
if (theBody == null) {
return "";
}
String retVal = theBody.trim();
@ -774,7 +810,10 @@ public class Controller {
});
}
}
theModel.put("conf", conformance);
theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED);
return conformance;
}

View File

@ -11,7 +11,7 @@
<bean class="ca.uhn.fhir.to.TesterConfig">
<property name="servers">
<list>
<value>test, TEST, http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/</value>
<!-- <value>test, TEST, http://uhnvesb01d.uhn.on.ca:25180/uhn-fhir-service-1.2/</value> -->
<value>home , Localhost Server , http://localhost:8887/fhir/context </value>
<value>hi , Health Intersections , http://fhir.healthintersections.com.au/open</value>
<value>furore , Spark - Furore Reference Server , http://spark.furore.com/fhir</value>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head th:include="tmpl-head :: head">
<title>About This Server</title>
</head>
<body>
<div th:replace="tmpl-navbar-top :: top" ></div>
<div class="container-fluid">
<div class="row">
<div th:replace="tmpl-navbar-left :: left" ></div>
<div class="col-sm-9 col-sm-offset-3 col-md-9 col-md-offset-3 main">
<div th:replace="tmpl-banner :: banner"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">About This Server</h3>
</div>
<div class="panel-body">
About...
</div>
</div>
</div>
</div>
</div>
<div th:replace="tmpl-footer :: footer" ></div>
</body>
</html>

View File

@ -67,7 +67,7 @@
Retrieve the server's <b>conformance</b> statement.
</div>
<div class="row-fluid">
<div class="col-sm-2 form-group">
<div class="col-sm-3 form-group">
<button type="button" id="fetch-conformance-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-dot-circle-o"></i>
@ -92,7 +92,7 @@
the server.
</div>
<div class="row-fluid top-buffer">
<div class="col-sm-2">
<div class="col-sm-3">
<button type="button" id="server-history-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-calendar"></i>
@ -152,14 +152,14 @@
store all resources within a single atomic transaction.
</div>
<div class="row-fluid">
<div class="col-sm-2">
<div class="col-sm-3">
<button type="button" id="transaction-btn"
data-loading-text="Processing &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-files-o"></i>
Transaction
</button>
</div>
<div class='col-sm-10'>
<div class='col-sm-9'>
<div class="form-group">
<div class='input-group'>
<div class="input-group-addon">
@ -206,7 +206,7 @@
Show all of the tags currently in use on the server
</div>
<div class="row-fluid">
<div class="col-sm-2 form-group">
<div class="col-sm-3 form-group">
<button type="button" id="get-server-tags-btn"
data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" class="btn btn-primary btn-block">
<i class="fa fa-tags"></i>

View File

@ -43,16 +43,56 @@
</div>
</div>
<div class="panel panel-default" th:if="${resourceName.empty} == false">
<div class="panel-heading">
<h3 class="panel-title">Search</h3>
</div>
<div class="panel-body">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist" id="resource-nav-tabs">
<!-- Search Tab -->
<li th:if="${haveSearchParams}" >
<a href="#tab-search" role="tab" data-toggle="tab">Search</a>
</li>
<li th:if="${!haveSearchParams}" class="disabled">
<a href="#tab-search" role="tab">Search</a>
</li>
<!-- Queries Tab -->
<li th:if="${!queries.empty}">
<a href="#tab-queries" role="tab" data-toggle="tab">Queries</a>
</li>
<li th:if="${queries.empty}" class="disabled">
<a href="#tab-queries" role="tab">Queries</a>
</li>
<!-- CRUD Tab -->
<li th:class="(${!haveSearchParams} and ${queries.empty}) ? 'active'">
<a href="#tab-otheractions" role="tab" data-toggle="tab">CRUD Operations</a>
</li>
<!-- Tags Tab -->
<li>
<a href="#tab-tags" role="tab" data-toggle="tab">Tags</a>
</li>
</ul>
<script type="text/javascript">
$( document ).ready(function() {
for (var i = 0; i != 4; i++){
if (!$('#resource-nav-tabs li:eq('+i+')').hasClass('disabled')) {
$('#resource-nav-tabs li:eq('+i+') a').tab('show');
break;
}
}
});
</script>
<!-- Tab panes -->
<div class="tab-content">
<!-- *************************************************** -->
<!-- Search Tab -->
<!-- *************************************************** -->
<div class="tab-pane active" id="tab-search">
<!-- Search contents -->
<div class="container-fluid">
<div class="row-fluid">
<div class="col-sm-2">
<div class="col-sm-2" style="padding-left: 0px;">
<button type="button" id="search-btn" class="btn btn-primary btn-block"
data-loading-text="Searching &lt;i class='fa fa-spinner fa-spin'/&gt;" >
data-loading-text="Searching &lt;i class='fa fa-spinner fa-spin'/&gt;">
<span class="glyphicon glyphicon-search"></span>
Search
</button>
@ -62,6 +102,15 @@
$('#search-btn').click(function() {
var btn = $(this);
btn.button('loading');
var sd = $('#tab-search').find('input').each(function() {
if (this.id) {
if (this.id.substring(0,4) == 'inc_') {
this.name = '_include';
} else {
this.name = this.id;
}
}
});
$("#outerForm").attr("action", "search").submit();
});
</script>
@ -82,7 +131,7 @@
<div class="row-fluid">
<span th:each="include : ${includes}" class="includeCheckContainer">
<span class="includeCheckCheck">
<input type="checkbox" name="_include" th:value="${include}" th:id="'inc_' + ${include}" />
<input type="checkbox" th:value="${include}" th:id="'inc_' + ${include}" />
</span>
<span class="includeCheckName" th:text="${include}"/>
</span>
@ -99,30 +148,30 @@
<div class="input-group-addon">
Limit
</div>
<input type="text" class="form-control" name="resource-search-limit" placeholder="max # returned"/>
<input type="text" class="form-control" id="resource-search-limit" placeholder="max # returned"/>
</div>
</div>
</div>
</div>
<!--
<div class="col-sm-1">
<button type="button" class="btn btn-success btn-block">Add
<span class="glyphicon glyphicon-plus"></span>
</button>
</div>
-->
<br clear="all"/>
</div>
</div>
</div>
</div>
<div class="panel panel-default" th:if="${resourceName.empty} == false">
<div class="panel-heading">
<h3 class="panel-title">Operations</h3>
<!-- End of search Tab -->
<!-- *************************************************** -->
<!-- Queries Tab -->
<!-- *************************************************** -->
<div class="tab-pane" id="tab-queries">
<div th:replace="tmpl-queries :: queries-list" ></div>
</div>
<div class="panel-body">
<!-- End of queries tab -->
<!-- *************************************************** -->
<!-- Other Operations Tab -->
<!-- *************************************************** -->
<div class="tab-pane" id="tab-otheractions">
<div class="container-fluid">
<!-- Read/VRead -->
@ -416,9 +465,18 @@
</script>
</div>
</div>
</div>
<!-- End of other operations tab -->
<!-- *************************************************** -->
<!-- Queries Tab -->
<!-- *************************************************** -->
<div class="tab-pane" id="tab-tags">
<div class="container-fluid">
<!-- Get Tags -->
<br clear="all"/>
<div class="row-fluid">
Show all of the tags currently in use on the server for this resource type. If an ID is specified,
returns only tags posted to the given version. If an ID and a version are specified,
@ -465,13 +523,14 @@
});
</script>
</div>
</div>
</div>
</div>
<!-- End of queries tab -->
</div>
<!-- End of tab pane -->
</div>
</div>
</div>

View File

@ -76,7 +76,11 @@
<span class="glyphicon glyphicon glyphicon-chevron-left"></span>
Response
</td>
<td th:text="${resultStatus}">HTTP 200 OK</td>
<td>
<i th:if="${resultStatus.contains(' 2')}" class="fa fa-check" style="color:#4E4;"/>
<i th:if="${!resultStatus.contains(' 2')}" class="fa fa-warning" style="color:#E44;"/>
<th:block th:text="${resultStatus}"/>
</td>
</tr>
<tr>
<td>Response Headers</td>
@ -167,10 +171,14 @@
<tbody>
<tr th:each="entry : ${bundle.entries}">
<td style="white-space: nowrap;">
<button class="btn btn-primary btn-xs" th:onclick="'readFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart,'')} + '\');'" type="submit" name="action" value="read" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ><i class="fa fa-book"></i> Read</button>
<button class="btn btn-primary btn-xs" th:onclick="'updateFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart, '')} + '\');'" type="submit" name="action" value="home"><i class="fa fa-pencil" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ></i> Update</button>
<th:block th:if="${entry.resource} != null">
<button class="btn btn-primary btn-xs" th:onclick="'readFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart,'')} + '\');'" type="submit" name="action" value="read" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ><i class="fa fa-book"></i> Read</button>
<button class="btn btn-primary btn-xs" th:onclick="'updateFromEntriesTable(this, \'' + ${entry.resource.id.resourceType} + '\', \'' + ${entry.resource.id.idPart} + '\', \'' + ${#strings.defaultString(entry.resource.id.versionIdPart, '')} + '\');'" type="submit" name="action" value="home"><i class="fa fa-pencil" data-loading-text="Loading &lt;i class='fa fa-spinner fa-spin'/&gt;" ></i> Update</button>
</th:block>
</td>
<td>
<th:block th:if="${entry.resource} != null" th:text="${entry.resource.id.toUnqualified()}"/>
</td>
<td><th:block th:text="${entry.resource.id.toUnqualified()}"/></td>
<td><small th:text="${entry.title}"/></td>
<td th:if="${entry.updated.value} == null"></td>
<td th:if="${entry.updated.value} != null and ${entry.updated.today} == true" th:text="${#dates.format(entry.updated.value, 'HH:mm:ss')}"></td>

View File

@ -63,7 +63,7 @@
<h4>Server</h4>
<ul class="nav nav-sidebar">
<li th:class="${resourceName.empty} ? 'active' : ''">
<li th:class="(${notHome} == null and ${resourceName} != null and ${resourceName.empty}) ? 'active' : ''">
<a href="#" onclick="doAction(this, 'home', null);">Server Home/Actions</a>
</li>
</ul>

View File

@ -0,0 +1,4 @@
<ul th:fragment="farright">
<li><a href="https://github.com/jamesagnew/hapi-fhir"><i class="fa fa-github topbarIcon" />&nbsp;Source Code</a></li>
<li><a href="about"><i class="fa fa-question-circle topbarIcon" />&nbsp;About This Server</a></li>
</ul>

View File

@ -11,26 +11,29 @@
</button>
<a class="navbar-brand"
th:href="'home?encoding=' + ${encoding} + '&amp;pretty=' + ${pretty}">
<i class="fa fa-home" /> HAPI FHIR
<i class="fa fa-home topbarIcon" /> HAPI FHIR
</a>
<a class="navbar-left navbarBreadcrumb hidden-xs hidden-sm"
th:if="${resourceName.empty} == false"
th:if="${resourceName} != null and ${resourceName.empty} == false"
th:href="'resource?encoding=' + ${encoding} + '&amp;pretty=' + ${pretty} + '&amp;resource=' + ${resourceName}">
<span class="glyphicon glyphicon-chevron-right"></span> <span
th:text="'Resource[' + ${resourceName} + ']'"></span>
th:text="${resourceName}"></span>
</a>
<div class="navbar-left navbarBreadcrumb hidden-xs hidden-sm"
th:if="${outcomeDescription} != null">
<span class="glyphicon glyphicon-chevron-right"></span> <span
th:text="${outcomeDescription}"></span>
</div>
<div class="navbar-left navbarBreadcrumb hidden-xs hidden-sm"
th:if="${extraBreadcrumb} != null">
<span class="glyphicon glyphicon-chevron-right"></span>&nbsp;<span
th:text="${extraBreadcrumb}"></span>
</div>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li class="dropdown"><a href="#" class="dropdown-toggle"
data-toggle="dropdown"> <span id="serverSelectorFhirIcon" class="glyphicon glyphicon-fire" style="color: #66AAFF" />
<span id="serverSelectorName" th:text="'Server: ' + ${baseName}" /> <span class="caret" />
</a>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span id="serverSelectorFhirIcon" class="glyphicon glyphicon-fire topbarIcon" />&nbsp;<span id="serverSelectorName" th:text="'Server: ' + ${baseName}" />&nbsp;<span class="caret" /></a>
<ul class="dropdown-menu" role="menu">
<li th:each="serverEntry : ${serverEntries}">
<a th:href="'javascript:selectServer(\'' + ${serverEntry.key} + '\');'">
@ -40,10 +43,9 @@
<th:block th:text="${serverEntry.value}"/>
</a>
</li>
</ul></li>
<li><a href="https://github.com/jamesagnew/hapi-fhir"><i
class="fa fa-github" />&nbsp;Source Code</a></li>
<li><a href="#">About</a></li>
</ul>
</li>
<th:block th:include="tmpl-navbar-top-farright :: farright"/>
</ul>
</div>
</div>

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<div th:fragment="queries-list" class="container-fluid">
<div th:each="query,queryIterStat : ${queries}" class="panel panel-default" th:id="'search-div-' + ${queryIterStat.index}" >
<div class="panel-heading">
<div class="pull-right">
<button type="button" th:id="'search-btn-' + ${queryIterStat.index}" class="btn btn-primary btn-block"
data-loading-text="Searching &lt;i class='fa fa-spinner fa-spin'/&gt;">
&nbsp;<span class="glyphicon glyphicon-search"></span>
Execute
&nbsp;
</button>
<script type="text/javascript">
var searchButtonName = '<th:block th:text="'search-btn-' + ${queryIterStat.index}"/>';
$('#' + searchButtonName).click(function() {
var btn = $(this);
btn.button('loading');
var searchDiv = '<th:block th:text="'search-div-' + ${queryIterStat.index}"/>';
var sd = $('#' + searchDiv).find('input').each(function() {
this.name = this.id;
});
$("#outerForm").attr("action", "search").submit();
});
</script>
</div>
<h4>
Query
<small th:if="${!query.name.empty}" th:text="${query.name.value}"/>
</h4>
</div>
<div class="panel-body">
<p th:if="${!query.documentation.empty}" th:text="${query.documentation.value}"></p>
<div th:each="param,paramIterStat : ${query.parameter}"
th:id="'search-param-rowopts-' + ${queryIterStat.index} + '-' + ${paramIterStat.index}"
th:class="'row queryParameter ' + (${paramIterStat.odd} ? 'queryParameterOdd')">
<div class="col-sm-6">
<div class="searchParamDescription">
<div>
<th:block th:text="${param.documentation}" />
<th:block th:if="${!param.getUndeclaredExtensionsByUrl(requiredParamExtension).empty}">
<span style="color:#F88;" th:if="${param.getUndeclaredExtensionsByUrl(requiredParamExtension).get(0).value.value}">
(required)
</span>
<span th:if="${!param.getUndeclaredExtensionsByUrl(requiredParamExtension).get(0).value.value}">
(optional)
</span>
</th:block>
</div>
</div>
</div>
<script type="text/javascript">
var type = '<th:block th:text="${param.type.value}"/>';
var name = '<th:block th:text="${param.name}"/>';
var chain = new Array();
<th:block th:each="chain : ${param.chain}">
chain.push('<th:block th:text="${chain}"/>');
</th:block>
var rowNum = <th:block th:text="${paramIterStat.index}"/>;
var containerRowNum = <th:block th:text="${queryIterStat.index}"/> + '-' + rowNum;
addSearchControls(type, name, chain, containerRowNum, rowNum);
</script>
<br clear="all"/>
</div>
</div>
</div>
</div>
</html>

View File

@ -147,6 +147,7 @@ body .syntaxhighlighter .line {
.sidebar {
display: none;
}
@media (min-width: 768px) {
.sidebar {
position: fixed;
@ -178,6 +179,10 @@ body .syntaxhighlighter .line {
background-color: #428bca;
}
.nav-tabs li.active A {
background: #f5f5f5;
font-weight: bold;
}
/*
* Main content
@ -220,6 +225,18 @@ body .syntaxhighlighter .line {
border-radius: 50%;
}
DIV.queryParameter {
padding-top: 3px;
padding-bottom: 3px;
border-radius: 6px;
margin-left: 5px;
margin-right: 5px;
}
DIV.queryParameterOdd {
background: #EEE;
}
TABLE.resultTable TR TD:FIRST-CHILD,
TABLE.requestTable TR TD:FIRST-CHILD {
font-weight: bold;
@ -272,6 +289,14 @@ DIV.searchParamSeparator {
font-size: 0.9em !important;
}
DIV.tab-pane DIV.container-fluid {
padding-top: 15px;
}
.topbarIcon {
color: #66AAFF;
}
DIV.top-buffer {
margin-top: 2px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,23 +1,4 @@
function addChildParam(searchParam, nextExt) {
var childParam = new Object();
for (var extIdx = 0; extIdx < nextExt.extension.length; extIdx++) {
var childExt = nextExt.extension[extIdx];
if (childExt.url == "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParamName") {
childParam.name = childExt.valueString;
}
if (childExt.url == "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParamDescription") {
childParam.documentation = childExt.valueString;
}
if (childExt.url == "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParamType") {
childParam.type = childExt.valueCode;
}
if (childExt.url == "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParam") {
addChildParam(childParam, childExt);
}
}
searchParam.childParam = childParam;
}
var numRows = 0;
function addSearchParamRow() {
@ -51,17 +32,6 @@ function addSearchParamRow() {
$('<option />', { value: nextName }).text(searchParam.name + ' - ' + searchParam.documentation)
);
if (restResource._searchParam && restResource._searchParam[i] != null) {
if (restResource._searchParam[i].extension) {
for (var j = 0; j < restResource._searchParam[i].extension.length; j++) {
var nextExt = restResource._searchParam[i].extension[j];
if (nextExt.url == "http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#additionalParam") {
addChildParam(searchParam, nextExt);
}
}
}
}
}
/*
restResource.searchParam.forEach(function(searchParam){
@ -89,49 +59,38 @@ function updateSearchDateQualifier(qualifierBtn, qualifierInput, qualifier) {
}
}
function addSearchControls(searchParam, searchParamName, containerRowNum, rowNum, isChild) {
if (searchParam.childParam || isChild) {
$('#search-param-rowopts-' + containerRowNum).append(
$('<br clear="all"/>'),
$('<div class="searchParamSeparator"/>'),
$('<div />', { 'class': 'col-sm-6' }).append(
$('<div class="searchParamDescription"/>').append(
$('<div />').text(searchParam.documentation)
)
)
);
}
function addSearchControls(theSearchParamType, theSearchParamName, theSearchParamChain, theContainerRowNum, theRowNum) {
if (searchParam.chain && searchParam.chain.length > 0) {
$('#search-param-rowopts-' + containerRowNum).append(
$('<input />', { name: 'param.' + rowNum + '.qualifier', type: 'hidden', value: '.' + searchParam.chain[0] })
if (theSearchParamChain && theSearchParamChain.length > 0) {
$('#search-param-rowopts-' + theContainerRowNum).append(
$('<input />', { id: 'param.' + theRowNum + '.qualifier', type: 'hidden', value: '.' + theSearchParamChain[0] })
);
}
$('#search-param-rowopts-' + containerRowNum).append(
$('<input />', { name: 'param.' + rowNum + '.name', type: 'hidden', value: searchParam.name })
$('#search-param-rowopts-' + theContainerRowNum).append(
$('<input />', { id: 'param.' + theRowNum + '.name', type: 'hidden', value: theSearchParamName })
);
if (searchParam.type == 'token') {
$('#search-param-rowopts-' + containerRowNum).append(
if (theSearchParamType == 'token') {
$('#search-param-rowopts-' + theContainerRowNum).append(
$('<div />', { 'class': 'col-sm-3' }).append(
$('<input />', { name: 'param.' + rowNum + '.0', placeholder: 'system/namespace', type: 'text', 'class': 'form-control' })
$('<input />', { id: 'param.' + theRowNum + '.0', placeholder: 'system/namespace', type: 'text', 'class': 'form-control' })
),
$('<div />', { 'class': 'col-sm-3' }).append(
$('<input />', { name: 'param.' + rowNum + '.1', type: 'hidden', value: '|' }),
$('<input />', { name: 'param.' + rowNum + '.2', placeholder: 'value', type: 'text', 'class': 'form-control' })
$('<input />', { id: 'param.' + theRowNum + '.1', type: 'hidden', value: '|' }),
$('<input />', { id: 'param.' + theRowNum + '.2', placeholder: 'value', type: 'text', 'class': 'form-control' })
)
);
} else if (searchParam.type == 'string' || searchParam.type == 'number') {
$('#search-param-rowopts-' + containerRowNum).append(
} else if (theSearchParamType == 'string' || theSearchParamType == 'number') {
$('#search-param-rowopts-' + theContainerRowNum).append(
$('<div />', { 'class': 'col-sm-3' }).append(
$('<input />', { name: 'param.' + rowNum + '.0', placeholder: 'value', type: 'text', 'class': 'form-control' })
$('<input />', { id: 'param.' + theRowNum + '.0', placeholder: 'value', type: 'text', 'class': 'form-control' })
)
);
} else if (searchParam.type == 'date') {
var qualifier = $('<input />', {type:'hidden', name:'param.'+rowNum+'.0', id:'param.'+rowNum+'.0'});
} else if (theSearchParamType == 'date') {
var qualifier = $('<input />', {type:'hidden', id:'param.'+theRowNum+'.0', id:'param.'+theRowNum+'.0'});
if (/date$/.test(searchParam.name)) {
if (/date$/.test(theSearchParamName)) {
var input = $('<div />', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DD' });
} else {
var input = $('<div />', { 'class':'input-group date', 'data-date-format':'YYYY-MM-DDTHH:mm:ss' });
@ -139,7 +98,7 @@ function addSearchControls(searchParam, searchParamName, containerRowNum, rowNum
var qualifierDiv = $('<div />');
input.append(
qualifierDiv,
$('<input />', { type:'text', 'class':'form-control', name: 'param.' + rowNum + '.1' }),
$('<input />', { type:'text', 'class':'form-control', id: 'param.' + theRowNum + '.1' }),
$('<div />', { 'class':'input-group-addon', 'style':'padding:6px;'} ).append(
$('<i />', { 'class':'fa fa-chevron-circle-down'})
)
@ -168,7 +127,7 @@ function addSearchControls(searchParam, searchParamName, containerRowNum, rowNum
)
);
$('#search-param-rowopts-' + containerRowNum).append(
$('#search-param-rowopts-' + theContainerRowNum).append(
qualifier,
$('<div />', { 'class': 'col-sm-4' }).append(
input
@ -176,21 +135,6 @@ function addSearchControls(searchParam, searchParamName, containerRowNum, rowNum
);
}
if (searchParam.childParam) {
$('#search-param-rowopts-' + containerRowNum).append(
/*
$('<br clear="all"/>'),
$('<div style="height: 5px;"/>'),
$('<div />', { 'class': 'col-sm-6' }).append(
$('<span>' + searchParam.childParam.documentation + '</span>')
),
*/
$('<input />', { name: 'param.' + rowNum + '.0.type', type: 'hidden', value: searchParam.childParam.type })
);
addSearchControls(searchParam.childParam, searchParamName, containerRowNum, rowNum + '.0', true);
}
}
function handleSearchParamTypeChange(select, params, rowNum) {
@ -205,7 +149,7 @@ function handleSearchParamTypeChange(select, params, rowNum) {
$('<input />', { name: 'param.' + rowNum + '.type', type: 'hidden', value: searchParam.type })
);
addSearchControls(searchParam, searchParam.name, rowNum, rowNum, false);
addSearchControls(searchParam.type, searchParam.name, searchParam.chain, rowNum, rowNum);
select.prevVal = newVal;
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.test;
import java.lang.annotation.Documented;
import java.util.List;
import java.util.Set;
@ -18,6 +19,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Organization;
@ -164,6 +166,7 @@ public class OverlayTestApp {
public static class ProviderWithRequiredAndOptional implements IResourceProvider {
@Description(shortDefinition="This is a query by date!")
@Search
public List<DiagnosticReport> findDiagnosticReportsByPatient (
@RequiredParam(name=DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) IdentifierDt thePatientId,
@ -174,6 +177,7 @@ public class OverlayTestApp {
return null;
}
@Description(shortDefinition="This is a query by issued.. blah blah foo bar blah blah")
@Search
public List<DiagnosticReport> findDiagnosticReportsByPatientIssued (
@RequiredParam(name=DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) IdentifierDt thePatientId,