-
Notifications
You must be signed in to change notification settings - Fork 26
Creating a new Resource Definition
- Add FHIR Create method
- Add FHIR Read method
- Add FHIR Search method
-
Test Definitions
- ./it-tests/src/test/groovy/gov/nist/toolkit/itTests/fhir/TestClientLocationSpec.groovy
- ./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/create/location.json
- ./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/create/testplan.xml
- ./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/index.idx
- ./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/query/testplan.xml
- ./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/read/testplan.xml
- ./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/readme.txt
- Note: The following lines may need to be committed out, if the created resource doesn’t contain history.
Examples shown on this page are focused on adding support for the FHIR Location Resource. This example is generated as support is starting to be developed for mCSD Location Option Message Semantics profile (section 3.Y1.4.1.2.3).
The individual changes are made to iheos-toolkit2 at https://github.com/usnistgov/iheos-toolkit2.git in commit id - fe4d76297d5fca1ca06690f700cefd9429735a76???????.
All code described below is added to the fhir module in Toolkit.
This has the following parts:
-
Creating a ResourceProvider class for the Location resource
-
Adding a Create method
-
Adding hooks to index the new resource instance in the resource db index
-
Adding the ResourceProvider class to the configuration
This is the handler for all processing on the Location resource. The class name is LocationResourceProvider and in the package gov.nist.toolkit.fhir.server.resourceProvider.
This class is defined by
class LocationResourceProvider implements IResourceProvider {
}
This class must announce what Resource it manages:
@Override
Class<? extends IBaseResource> getResourceType() {
return Location.class
}
@Create()
public MethodOutcome createLocation(@ResourceParam Location theLocation,
RequestDetails requestDetails) {
validateResource(theLocation);
return new ToolkitResourceProvider(getResourceType(), requestDetails).createOperation(theLocation)
}
ToolkitResourceProvider is a utility class containing support code for building resource provider classes. The RequestDetails class, defined by HAPI, is an extension of the HTTPRequest class from the Servlet specification. The extensions include REST specific things and FHIR specific things.
From within the utility ToolkitResourceProvider.createOperation is code to both add the new resource instance to the resource database and add its details to index that supports searches. The next step is to code the indexer: gov.nist.toolkit.fhir.resourceIndexer.LocationIndexer. Note that there is support code that knows to look in this package to find resource indexers so if you put your indexer in a different place in the package tree then the index engine must be modified to look there. The class name must be a composite of the resource name and the string Indexer as shown in this example.
class LocationIndexer implements IResourceIndexer {
/**
* Index the Location index attributes
* @param json - JsonSlurper representation of index
* @param simResource - details about where the index will be stored
* @return newly built index
*/
@Override
ResourceIndex build(DomainResource theResource, String id) {
ResourceIndex resourceIndex = new Base().build(theResource, id) // adds the type and id to the index
// Add specialization here - the other fields you want to be able to search on
// If they are not added here then they will not be found in the Search operation
Location location = (Location) theResource
// Add city name
// Note that we use the name Location.SP_ADDRESS_CITY for the attribute. This definition comes out
// of HAPI and actually is the string that will be used in the URL when a Search operation is coded
// so this string is specified in the FHIR standard. We use it here for the internal
// index as well.
resourceIndex.add(new ResourceIndexItem(Location.SP_ADDRESS_CITY, location.getAddress().city))
return resourceIndex
}
}
The ResourceProvider class must be added to the HAPI configuration so it can find it. This is done by adding a line to the class gov.nist.toolkit.fhir.servlet.RestfulServlet.
import gov.nist.toolkit.fhir.server.resourceProvider.LocationResourceProvider;
resourceProviders.add(new LocationResourceProvider());
The Read method takes a resource type and id and returns the contents of the resource. Add the Read method to the ResourceProvider class (LocationResourceProvider.groovy in this case).
@Read()
public Location getResourceById(@IdParam IdType theId,
RequestDetails requestDetails) {
ToolkitResourceProvider tk = new ToolkitResourceProvider(getResourceType(), requestDetails)
// Find the resource in the resource database
File f = tk.readOperation(theId)
try {
// The local storage is in JSON - return it as a Location resource model object
return tk.jsonParser.parseResource(getResourceType(), new FileReader(f));
} catch (FileNotFoundException e) {
throw new InternalErrorException("File " + f + " not found");
}
}
The Read operation is a search based on resource type and ID.
The Search method is the implementation of all queries against a resource. In the call parameters to the method below are listed the Required and Optional parameters of the search. These should follow the requirements of the FHIR standard and the profile of interest.
@Search()
public List<Location> getLocation(
@RequiredParam(name = Location.SP_ADDRESS_CITY) StringParam theAddressCity,
/* @OptionalParam(name = Patient.SP_GIVEN) StringParam theGivenName, */
RequestDetails requestDetails) {
ToolkitResourceProvider tk = new ToolkitResourceProvider(getResourceType(), requestDetails)
// query builder
BooleanQuery.Builder builder = new BooleanQuery.Builder()
Term term
TermQuery termQuery
// Add a required term to the query builder
term = new Term(Location.SP_ADDRESS_CITY, theAddressCity)
termQuery = new TermQuery(term)
builder.add ( termQuery, BooleanClause.Occur.MUST ) // here MUST indicates it is a required parameter
/* if (theGivenName) {
term = new Term(Patient.SP_GIVEN, theGivenName.value)
termQuery = new TermQuery(term)
builder.add(termQuery, BooleanClause.Occur.SHOULD) // this is the coding for an optional parameter
} */
// BaseQuery.execute runs the Lucene query
// tk.searchResults takes that output and generates a FHIR bundle to return
return tk.searchResults(new BaseQuery(tk.simContext).execute(builder))
}
In the above code is shown a second, optional parameter (commented out) just to provide an example of how to code an optional parameter. The search is built up by adding each parameter to the query builder. Note that each parameter is labeled with either Required or Optional status which matches the semantics of FHIR searches.
package gov.nist.toolkit.itTests.fhir
import ca.uhn.fhir.context.FhirContext
import gov.nist.toolkit.itTests.support.FhirSpecification
import gov.nist.toolkit.results.client.Result
import gov.nist.toolkit.results.client.TestInstance
import gov.nist.toolkit.simcommon.client.SimId
import gov.nist.toolkit.simcommon.server.SimDb
import gov.nist.toolkit.testengine.scripts.BuildCollections
import gov.nist.toolkit.toolkitApi.SimulatorBuilder
import spock.lang.Shared
/**
*
*/
class TestClientLocationSpec extends FhirSpecification {
@Shared SimulatorBuilder spi
@Shared def testSession = 'bill'
@Shared def simIdName = 'myfhirsys'
@Shared def siteName = "${testSession}__${simIdName}"
@Shared SimId simId = new SimId(testSession, simIdName).forFhir()
@Shared FhirContext ourCtx = FhirContext.forDstu3()
@Shared SimDb simDb
@Shared TestInstance testInstance = new TestInstance('FhirTestClientCreateLocation')
def setupSpec() {
startGrizzlyWithFhir('8889')
// Initialize remote api for talking to toolkit on Grizzly
// Needed to build simulators
spi = getSimulatorApi(remoteToolkitPort)
// local customization
new BuildCollections().init(null)
// SimId must be translated into SPI variety
spi.delete(spiSimId(simId)) // if you use the form spi.delete(simIdName, testSession) it will look in the SimDb instead of ResDb
spi.createFhirServer(simId.id, simId.user, 'default')
}
def 'do create'() {
when:
def sections = ['create']
def params = [ :]
List<Result> results = api.runTest(testSession, siteName, testInstance, sections, params, true)
then:
results.size() == 1
results.get(0).passed()
}
def 'do read'() {
when:
def sections = ['read']
def params = [ :]
List<Result> results = api.runTest(testSession, siteName, testInstance, sections, params, true)
then:
results.size() == 1
results.get(0).passed()
}
def 'do query'() {
when:
def sections = ['query']
def params = [ :]
List<Result> results = api.runTest(testSession, siteName, testInstance, sections, params, true)
then:
results.size() == 1
results.get(0).passed()
}
}
./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/create/location.json
{
"resourceType": "Location",
"id": "1",
"text": {
"status": "generated",
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">Burgers UMC, South Wing, second floor</div>"
},
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/location-alias",
"valueString": "Burgers University Medical Center, South Wing, second floor"
},
{
"url": "http://hl7.org/fhir/StructureDefinition/location-alias",
"valueString": "BU MC, SW, F2"
}
],
"identifier": [
{
"value": "B1-S.F2"
}
],
"status": "active",
"name": "South Wing, second floor",
"description": "Second floor of the Old South Wing, formerly in use by Psychiatry",
"mode": "instance",
"telecom": [
{
"system": "phone",
"value": "2328",
"use": "work"
},
{
"system": "fax",
"value": "2329",
"use": "work"
},
{
"system": "email",
"value": "second wing admissions"
},
{
"system": "url",
"value": "http://sampleorg.com/southwing",
"use": "work"
}
],
"address": {
"use": "work",
"line": [
"Galapagosweg 91, Building A"
],
"city": "Den Burg",
"postalCode": "9105 PZ",
"country": "NLD"
},
"physicalType": {
"coding": [
{
"system": "http://hl7.org/fhir/location-physical-type",
"code": "wi",
"display": "Wing"
}
]
},
"position": {
"longitude": -83.6945691,
"latitude": 42.25475478,
"altitude": 0
},
"managingOrganization": {
"reference": "Organization/f001"
},
"endpoint": [
{
"reference": "Endpoint/example"
}
]
}
./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/create/testplan.xml
<TestPlan>
<Test>FhirTestClientCreateLocation/create</Test>
<TestStep id="create">
<ExpectedStatus>Success</ExpectedStatus>
<FhirCreateTransaction>
<ResourceFile>location.json</ResourceFile>
<UrlExtension>/Location</UrlExtension>
</FhirCreateTransaction>
</TestStep>
</TestPlan>
create
read
query
./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/query/testplan.xml
<TestPlan>
<Test>FhirTestClientCreate/query</Test>
<TestStep id="query">
<ExpectedStatus>Success</ExpectedStatus>
<FhirQueryTransaction>
<UseReport test="FhirTestClientCreate" section="create" step="create" reportName="Url" useAs="Url"/>
<QueryParams>?family=Chalmers</QueryParams>
</FhirQueryTransaction>
</TestStep>
</TestPlan>
./it-tests/src/test/resources/war/toolkitx/testkit/tests/FhirTestClientCreateLocation/read/testplan.xml
<TestPlan>
<Test>FhirTestClientCreate/read</Test>
<TestStep id="read">
<ExpectedStatus>Success</ExpectedStatus>
<FhirReadTransaction>
<UseReport test="FhirTestClientCreate" section="create" step="create" reportName="Ref" useAs="Ref"/>
</FhirReadTransaction>
</TestStep>
</TestPlan>
blank
Toolkit
Downloads
Installing Toolkit
Configuring Toolkit for Imaging Tests
Reporting Toolkit Installation Problems
Environment
Test Session
Conformance Test Tool
Writing Conformance Tests
Overview of Imaging Tests
Test Context Definition
Launching Conformance Tool from Gazelle
Inspector
External Cache
Support Tools
Test Organization
Configuring Test Kits
Managing Multiple Test Kits
SAML Validation against Gazelle
Renaming Toolkit
Toolkit API
Managing system configurations
Configuring Toolkit for Connectathon
Developer's blog