package example; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.annotation.AddTags; import ca.uhn.fhir.rest.annotation.At; import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; 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.DeleteTags; import ca.uhn.fhir.rest.annotation.Elements; import ca.uhn.fhir.rest.annotation.GetTags; import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.OptionalParam; 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.Sort; import ca.uhn.fhir.rest.annotation.TagListParam; import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.TransactionParam; 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.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParamPrefixEnum; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; @SuppressWarnings("unused") public abstract class RestfulPatientResourceProviderMore implements IResourceProvider { public interface ITestClient extends IBasicClient { @Search List<Patient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theParam); } private boolean detectedVersionConflict; private boolean conflictHappened; private boolean couldntFindThisId; private FhirContext myContext; //START SNIPPET: searchAll @Search public List<Organization> getAllOrganizations() { List<Organization> retVal=new ArrayList<Organization>(); // populate this return retVal; } //END SNIPPET: searchAll //START SNIPPET: updateEtag @Update public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) { String resourceId = theId.getIdPart(); String versionId = theId.getVersionIdPart(); // this will contain the ETag String currentVersion = "1"; // populate this with the current version if (!versionId.equals(currentVersion)) { throw new ResourceVersionConflictException("Expected version " + currentVersion); } // ... perform the update ... return new MethodOutcome(); } //END SNIPPET: updateEtag //START SNIPPET: summaryAndElements @Search public List<Patient> search( SummaryEnum theSummary, // will receive the summary (no annotation required) @Elements Set<String> theElements // (requires the @Elements annotation) ) { return null; // todo: populate } //END SNIPPET: summaryAndElements //START SNIPPET: searchCompartment public class PatientRp implements IResourceProvider { @Override public Class<? extends IBaseResource> getResourceType() { return Patient.class; } @Search(compartmentName="Condition") public List<IBaseResource> searchCompartment(@IdParam IdType thePatientId) { List<IBaseResource> retVal=new ArrayList<IBaseResource>(); // populate this with resources of any type that are a part of the // "Condition" compartment for the Patient with ID "thePatientId" return retVal; } // .. also include other Patient operations .. } //END SNIPPET: searchCompartment //START SNIPPET: sort @Search public List<Patient> findPatients( @RequiredParam(name=Patient.SP_IDENTIFIER) StringParam theParameter, @Sort SortSpec theSort) { List<Patient> retVal=new ArrayList<Patient>(); // populate this // theSort is null unless a _sort parameter is actually provided if (theSort != null) { // The name of the param to sort by String param = theSort.getParamName(); // The sort order, or null SortOrderEnum order = theSort.getOrder(); // This will be populated if a second _sort was specified SortSpec subSort = theSort.getChain(); // ...apply the sort... } return retVal; } //END SNIPPET: sort //START SNIPPET: underlyingReq @Search public List<Patient> findPatients( @RequiredParam(name="foo") StringParam theParameter, HttpServletRequest theRequest, HttpServletResponse theResponse) { List<Patient> retVal=new ArrayList<Patient>(); // populate this return retVal; } //END SNIPPET: underlyingReq //START SNIPPET: referenceSimple @Search public List<DiagnosticReport> findDiagnosticReportsWithSubjet( @OptionalParam(name=DiagnosticReport.SP_SUBJECT) ReferenceParam theSubject ) { List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>(); // If the parameter passed in includes a resource type (e.g. ?subject:Patient=123) // that resource type is available. Here we just check that it is either not provided // or set to "Patient" if (theSubject.hasResourceType()) { String resourceType = theSubject.getResourceType(); if ("Patient".equals(resourceType) == false) { throw new InvalidRequestException("Invalid resource type for parameter 'subject': " + resourceType); } } if (theSubject != null) { // ReferenceParam extends IdType so all of the resource ID methods are available String subjectId = theSubject.getIdPart(); // .. populate retVal with DiagnosticReport resources having // subject with id "subjectId" .. } return retVal; } //END SNIPPET: referenceSimple //START SNIPPET: referenceWithChain @Search public List<DiagnosticReport> findReportsWithChain( @RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {Patient.SP_FAMILY, Patient.SP_GENDER}) ReferenceParam theSubject ) { List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>(); String chain = theSubject.getChain(); if (Patient.SP_FAMILY.equals(chain)) { String familyName = theSubject.getValue(); // .. populate with reports matching subject family name .. } if (Patient.SP_GENDER.equals(chain)) { String gender = theSubject.getValue(); // .. populate with reports matching subject gender .. } return retVal; } //END SNIPPET: referenceWithChain //START SNIPPET: referenceWithChainCombo @Search public List<DiagnosticReport> findReportsWithChainCombo ( @RequiredParam(name=DiagnosticReport.SP_SUBJECT, chainWhitelist= {"", Patient.SP_FAMILY}) ReferenceParam theSubject ) { List<DiagnosticReport> retVal=new ArrayList<DiagnosticReport>(); String chain = theSubject.getChain(); if (Patient.SP_FAMILY.equals(chain)) { String familyName = theSubject.getValue(); // .. populate with reports matching subject family name .. } if ("".equals(chain)) { String resourceId = theSubject.getValue(); // .. populate with reports matching subject with resource ID .. } return retVal; } //END SNIPPET: referenceWithChainCombo //START SNIPPET: referenceWithStaticChain @Search public List<Patient> findObservations( @RequiredParam(name=Observation.SP_SUBJECT+'.'+Patient.SP_IDENTIFIER) TokenParam theProvider ) { String system = theProvider.getSystem(); String identifier = theProvider.getValue(); // ...Do a search for all observations for the given subject... List<Patient> retVal=new ArrayList<Patient>(); // populate this return retVal; } //END SNIPPET: referenceWithStaticChain //START SNIPPET: referenceWithDynamicChain @Search() public List<Observation> findBySubject( @RequiredParam(name=Observation.SP_SUBJECT, chainWhitelist = {"", Patient.SP_IDENTIFIER, Patient.SP_BIRTHDATE}) ReferenceParam subject ) { List<Observation> observations = new ArrayList<Observation>(); String chain = subject.getChain(); if (Patient.SP_IDENTIFIER.equals(chain)) { // Because the chained parameter "subject.identifier" is actually of type // "token", we convert the value to a token before processing it. TokenParam tokenSubject = subject.toTokenParam(myContext); String system = tokenSubject.getSystem(); String identifier = tokenSubject.getValue(); // TODO: populate all the observations for the identifier } else if (Patient.SP_BIRTHDATE.equals(chain)) { // Because the chained parameter "subject.birthdate" is actually of type // "date", we convert the value to a date before processing it. DateParam dateSubject = subject.toDateParam(myContext); DateTimeType birthDate = new DateTimeType(dateSubject.getValueAsString()); // TODO: populate all the observations for the birthdate } else if ("".equals(chain)) { String resourceId = subject.getValue(); // TODO: populate all the observations for the resource id } return observations; } //END SNIPPET: referenceWithDynamicChain //START SNIPPET: read @Read() public Patient getResourceById(@IdParam IdType theId) { Patient retVal = new Patient(); // ...populate... retVal.addIdentifier().setSystem("urn:mrns").setValue("12345"); retVal.addName().setFamily("Smith").addGiven("Tester").addGiven("Q"); // ...etc... // if you know the version ID of the resource, you should set it and HAPI will // include it in a Content-Location header retVal.setId(new IdType("Patient", "123", "2")); return retVal; } //END SNIPPET: read //START SNIPPET: delete @Delete() public void deletePatient(@IdParam IdType theId) { // .. Delete the patient .. if (couldntFindThisId) { throw new ResourceNotFoundException("Unknown version"); } if (conflictHappened) { throw new ResourceVersionConflictException("Couldn't delete because [foo]"); } // otherwise, delete was successful return; // can also return MethodOutcome } //END SNIPPET: delete //START SNIPPET: deleteConditional @Delete() public void deletePatientConditional(@IdParam IdType theId, @ConditionalUrlParam String theConditionalUrl) { // Only one of theId or theConditionalUrl will have a value depending // on whether the URL receieved was a logical ID, or a conditional // search string if (theId != null) { // do a normal delete } else { // do a conditional delete } // otherwise, delete was successful return; // can also return MethodOutcome } //END SNIPPET: deleteConditional //START SNIPPET: history @History() public List<Patient> getPatientHistory( @IdParam IdType theId, @Since InstantType theSince, @At DateRangeParam theAt ) { List<Patient> retVal = new ArrayList<Patient>(); Patient patient = new Patient(); patient.addName().setFamily("Smith"); // Set the ID and version patient.setId(theId.withVersion("1")); // ...populate the rest... return retVal; } //END SNIPPET: history //START SNIPPET: vread @Read(version=true) public Patient readOrVread(@IdParam IdType theId) { Patient retVal = new Patient(); if (theId.hasVersionIdPart()) { // this is a vread } else { // this is a read } // ...populate... return retVal; } //END SNIPPET: vread //START SNIPPET: searchStringParam @Search() public List<Patient> searchByLastName(@RequiredParam(name=Patient.SP_FAMILY) StringParam theFamily) { String valueToMatch = theFamily.getValue(); if (theFamily.isExact()) { // Do an exact match search } else { // Do a fuzzy search if possible } // ...populate... Patient patient = new Patient(); patient.addIdentifier().setSystem("urn:mrns").setValue("12345"); patient.addName().setFamily("Smith").addGiven("Tester").addGiven("Q"); // ...etc... // Every returned resource must have its logical ID set. If the server // supports versioning, that should be set too String logicalId = "4325"; String versionId = "2"; // optional patient.setId(new IdType("Patient", logicalId, versionId)); /* * This is obviously a fairly contrived example since we are always * just returning the same hardcoded patient, but in a real scenario * you could return as many resources as you wanted, and they * should actually match the given search criteria. */ List<Patient> retVal = new ArrayList<Patient>(); retVal.add(patient); return retVal; } //END SNIPPET: searchStringParam //START SNIPPET: searchNamedQuery @Search(queryName="namedQuery1") public List<Patient> searchByNamedQuery(@RequiredParam(name="someparam") StringParam theSomeParam) { List<Patient> retVal = new ArrayList<Patient>(); // ...populate... return retVal; } //END SNIPPET: searchNamedQuery //START SNIPPET: searchComposite @Search() public List<Observation> searchByComposite( @RequiredParam(name=Observation.SP_CODE_VALUE_DATE, compositeTypes= {TokenParam.class, DateParam.class}) CompositeParam<TokenParam, DateParam> theParam) { // Each of the two values in the composite param are accessible separately. // In the case of Observation's name-value-date, the left is a string and // the right is a date. TokenParam observationName = theParam.getLeftValue(); DateParam observationValue = theParam.getRightValue(); List<Observation> retVal = new ArrayList<Observation>(); // ...populate... return retVal; } //END SNIPPET: searchComposite //START SNIPPET: searchIdentifierParam @Search() public List<Patient> searchByIdentifier(@RequiredParam(name=Patient.SP_IDENTIFIER) TokenParam theId) { String identifierSystem = theId.getSystem(); String identifier = theId.getValue(); List<Patient> retVal = new ArrayList<Patient>(); // ...populate... return retVal; } //END SNIPPET: searchIdentifierParam //START SNIPPET: searchOptionalParam @Search() public List<Patient> searchByNames( @RequiredParam(name=Patient.SP_FAMILY) StringParam theFamilyName, @OptionalParam(name=Patient.SP_GIVEN) StringParam theGivenName ) { String familyName = theFamilyName.getValue(); String givenName = theGivenName != null ? theGivenName.getValue() : null; List<Patient> retVal = new ArrayList<Patient>(); // ...populate... return retVal; } //END SNIPPET: searchOptionalParam //START SNIPPET: searchWithDocs @Description(shortDefinition="This search finds all patient resources matching a given name combination") @Search() public List<Patient> searchWithDocs( @Description(shortDefinition="This is the patient's last name - Supports partial matches") @RequiredParam(name=Patient.SP_FAMILY) StringParam theFamilyName, @Description(shortDefinition="This is the patient's given names") @OptionalParam(name=Patient.SP_GIVEN) StringParam theGivenName ) { List<Patient> retVal = new ArrayList<Patient>(); // ...populate... return retVal; } //END SNIPPET: searchWithDocs //START SNIPPET: searchMultiple @Search() public List<Observation> searchByObservationNames( @RequiredParam(name=Observation.SP_CODE) TokenOrListParam theCodings ) { // The list here will contain 0..* codings, and any observations which match any of the // given codings should be returned List<TokenParam> wantedCodings = theCodings.getValuesAsQueryTokens(); List<Observation> retVal = new ArrayList<Observation>(); // ...populate... return retVal; } //END SNIPPET: searchMultiple //START SNIPPET: searchMultipleAnd @Search() public List<Patient> searchByPatientAddress( @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts ) { // StringAndListParam is a container for 0..* StringOrListParam, which is in turn a // container for 0..* strings. It is a little bit weird to understand at first, but think of the // StringAndListParam to be an AND list with multiple OR lists inside it. So you will need // to return results which match at least one string within every OR list. List<StringOrListParam> wantedCodings = theAddressParts.getValuesAsQueryTokens(); for (StringOrListParam nextOrList : wantedCodings) { List<StringParam> queryTokens = nextOrList.getValuesAsQueryTokens(); // Only return results that match at least one of the tokens in the list below for (StringParam nextString : queryTokens) { // ....check for match... } } List<Patient> retVal = new ArrayList<Patient>(); // ...populate... return retVal; } //END SNIPPET: searchMultipleAnd //START SNIPPET: dates @Search() public List<Patient> searchByObservationNames( @RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theDate ) { ParamPrefixEnum prefix = theDate.getPrefix(); // e.g. gt, le, etc.. Date date = theDate.getValue(); // e.g. 2011-01-02 TemporalPrecisionEnum precision = theDate.getPrecision(); // e.g. DAY List<Patient> retVal = new ArrayList<Patient>(); // ...populate... return retVal; } //END SNIPPET: dates public void dateClientExample() { ITestClient client = provideTc(); //START SNIPPET: dateClient DateParam param = new DateParam(ParamPrefixEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"); List<Patient> response = client.getPatientByDob(param); //END SNIPPET: dateClient } //START SNIPPET: dateRange @Search() public List<Observation> searchByDateRange( @RequiredParam(name=Observation.SP_DATE) DateRangeParam theRange ) { Date from = theRange.getLowerBoundAsInstant(); Date to = theRange.getUpperBoundAsInstant(); List<Observation> retVal = new ArrayList<Observation>(); // ...populate... return retVal; } //END SNIPPET: dateRange private ITestClient provideTc() { return null; } @Override public Class<? extends IBaseResource> getResourceType() { return null; } //START SNIPPET: pathSpec @Search() public List<DiagnosticReport> getDiagnosticReport( @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) TokenParam theIdentifier, @IncludeParam(allow= {"DiagnosticReport:subject"}) Set<Include> theIncludes ) { List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>(); // Assume this method exists and loads the report from the DB DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier); // If the client has asked for the subject to be included: if (theIncludes.contains(new Include("DiagnosticReport:subject"))) { // The resource reference should contain the ID of the patient IIdType subjectId = report.getSubject().getReferenceElement(); // So load the patient ID and return it Patient subject = loadSomePatientFromDatabase(subjectId); report.getSubject().setResource(subject); } retVal.add(report); return retVal; } //END SNIPPET: pathSpec //START SNIPPET: revInclude @Search() public List<DiagnosticReport> getDiagnosticReport( @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) TokenParam theIdentifier, @IncludeParam() Set<Include> theIncludes, @IncludeParam(reverse=true) Set<Include> theReverseIncludes ) { return new ArrayList<DiagnosticReport>(); // populate this } //END SNIPPET: revInclude //START SNIPPET: pathSpecSimple @Search() public List<DiagnosticReport> getDiagnosticReport( @RequiredParam(name=DiagnosticReport.SP_IDENTIFIER) TokenParam theIdentifier, @IncludeParam(allow= {"DiagnosticReport:subject"}) String theInclude ) { List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>(); // Assume this method exists and loads the report from the DB DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier); // If the client has asked for the subject to be included: if ("DiagnosticReport:subject".equals(theInclude)) { // The resource reference should contain the ID of the patient IIdType subjectId = report.getSubject().getReferenceElement(); // So load the patient ID and return it Patient subject = loadSomePatientFromDatabase(subjectId); report.getSubject().setResource(subject); } retVal.add(report); return retVal; } //END SNIPPET: pathSpecSimple //START SNIPPET: quantity @Search() public List<Observation> getObservationsByQuantity( @RequiredParam(name=Observation.SP_VALUE_QUANTITY) QuantityParam theQuantity) { List<Observation> retVal = new ArrayList<Observation>(); ParamPrefixEnum prefix = theQuantity.getPrefix(); BigDecimal value = theQuantity.getValue(); String units = theQuantity.getUnits(); // .. Apply these parameters .. // ... populate ... return retVal; } //END SNIPPET: quantity private DiagnosticReport loadSomeDiagnosticReportFromDatabase(TokenParam theIdentifier) { return null; } private Patient loadSomePatientFromDatabase(IIdType theId) { return null; } //START SNIPPET: create @Create public MethodOutcome createPatient(@ResourceParam Patient thePatient) { /* * First we might want to do business validation. The UnprocessableEntityException * results in an HTTP 422, which is appropriate for business rule failure */ if (thePatient.getIdentifierFirstRep().isEmpty()) { /* It is also possible to pass an OperationOutcome resource * to the UnprocessableEntityException if you want to return * a custom populated OperationOutcome. Otherwise, a simple one * is created using the string supplied below. */ throw new UnprocessableEntityException("No identifier supplied"); } // Save this patient to the database... savePatientToDatabase(thePatient); // This method returns a MethodOutcome object which contains // the ID (composed of the type Patient, the logical ID 3746, and the // version ID 1) MethodOutcome retVal = new MethodOutcome(); retVal.setId(new IdType("Patient", "3746", "1")); // You can also add an OperationOutcome resource to return // This part is optional though: OperationOutcome outcome = new OperationOutcome(); outcome.addIssue().setDiagnostics("One minor issue detected"); retVal.setOperationOutcome(outcome); return retVal; } //END SNIPPET: create //START SNIPPET: createConditional @Create public MethodOutcome createPatientConditional( @ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) { if (theConditionalUrl != null) { // We are doing a conditional create // populate this with the ID of the existing resource which // matches the conditional URL return new MethodOutcome(); } else { // We are doing a normal create // populate this with the ID of the newly created resource return new MethodOutcome(); } } //END SNIPPET: createConditional //START SNIPPET: createClient @Create public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient); //END SNIPPET: createClient //START SNIPPET: updateConditional @Update public MethodOutcome updatePatientConditional( @ResourceParam Patient thePatient, @IdParam IdType theId, @ConditionalUrlParam String theConditional) { // Only one of theId or theConditional will have a value and the other will be null, // depending on the URL passed into the server. if (theConditional != null) { // Do a conditional update. theConditional will have a value like "Patient?identifier=system%7C00001" } else { // Do a normal update. theId will have the identity of the resource to update } return new MethodOutcome(); // populate this } //END SNIPPET: updateConditional //START SNIPPET: updatePrefer @Update public MethodOutcome updatePatientPrefer( @ResourceParam Patient thePatient, @IdParam IdType theId) { // Save the patient to the database // Update the version and last updated time on the resource IdType updatedId = theId.withVersion("123"); thePatient.setId(updatedId); InstantType lastUpdated = InstantType.withCurrentTime(); thePatient.getMeta().setLastUpdatedElement(lastUpdated); // Add the resource to the outcome, so that it can be returned by the server // if the client requests it MethodOutcome outcome = new MethodOutcome(); outcome.setId(updatedId); outcome.setResource(thePatient); return outcome; } //END SNIPPET: updatePrefer //START SNIPPET: updateRaw @Update public MethodOutcome updatePatientWithRawValue ( @ResourceParam Patient thePatient, @IdParam IdType theId, @ResourceParam String theRawBody, @ResourceParam EncodingEnum theEncodingEnum) { // Here, thePatient will have the parsed patient body, but // theRawBody will also have the raw text of the resource // being created, and theEncodingEnum will tell you which // encoding was used return new MethodOutcome(); // populate this } //END SNIPPET: updateRaw //START SNIPPET: update @Update public MethodOutcome updatePatient(@IdParam IdType theId, @ResourceParam Patient thePatient) { /* * First we might want to do business validation. The UnprocessableEntityException * results in an HTTP 422, which is appropriate for business rule failure */ if (thePatient.getIdentifierFirstRep().isEmpty()) { /* It is also possible to pass an OperationOutcome resource * to the UnprocessableEntityException if you want to return * a custom populated OperationOutcome. Otherwise, a simple one * is created using the string supplied below. */ throw new UnprocessableEntityException("No identifier supplied"); } String versionId = theId.getVersionIdPart(); if (versionId != null) { // If the client passed in a version number in an If-Match header, they are // doing a version-aware update. You may wish to throw an exception if the supplied // version is not the latest version. Note that as of DSTU2 the FHIR specification uses // ETags and If-Match to handle version aware updates, so PreconditionFailedException (HTTP 412) // is used instead of ResourceVersionConflictException (HTTP 409) if (detectedVersionConflict) { throw new PreconditionFailedException("Unexpected version"); } } // Save this patient to the database... savePatientToDatabase(theId, thePatient); // This method returns a MethodOutcome object which contains // the ID and Version ID for the newly saved resource MethodOutcome retVal = new MethodOutcome(); String newVersion = "2"; // may be null if the server is not version aware retVal.setId(theId.withVersion(newVersion)); // You can also add an OperationOutcome resource to return // This part is optional though: OperationOutcome outcome = new OperationOutcome(); outcome.addIssue().setDiagnostics("One minor issue detected"); retVal.setOperationOutcome(outcome); // If your server supports creating resources during an update if they don't already exist // (this is not mandatory and may not be desirable anyhow) you can flag in the response // that this was a creation as follows: // retVal.setCreated(true); return retVal; } //END SNIPPET: update //START SNIPPET: updateClient @Update public abstract MethodOutcome updateSomePatient(@IdParam IdType theId, @ResourceParam Patient thePatient); //END SNIPPET: updateClient //START SNIPPET: validate @Validate public MethodOutcome validatePatient(@ResourceParam Patient thePatient, @Validate.Mode ValidationModeEnum theMode, @Validate.Profile String theProfile) { // Actually do our validation: The UnprocessableEntityException // results in an HTTP 422, which is appropriate for business rule failure if (thePatient.getIdentifierFirstRep().isEmpty()) { /* It is also possible to pass an OperationOutcome resource * to the UnprocessableEntityException if you want to return * a custom populated OperationOutcome. Otherwise, a simple one * is created using the string supplied below. */ throw new UnprocessableEntityException("No identifier supplied"); } // This method returns a MethodOutcome object MethodOutcome retVal = new MethodOutcome(); // You may also add an OperationOutcome resource to return // This part is optional though: OperationOutcome outcome = new OperationOutcome(); outcome.addIssue().setSeverity(IssueSeverity.WARNING).setDiagnostics("One minor issue detected"); retVal.setOperationOutcome(outcome); return retVal; } //END SNIPPET: validate public static void main(String[] args) throws DataFormatException, IOException { //nothing } private void savePatientToDatabase(Patient thePatient) { // nothing } private void savePatientToDatabase(IdType theId, Patient thePatient) { // nothing } //START SNIPPET: metadataProvider public class CapabilityStatementProvider { @Metadata public CapabilityStatement getServerMetadata() { CapabilityStatement retVal = new CapabilityStatement(); // ..populate.. return retVal; } } //END SNIPPET: metadataProvider //START SNIPPET: metadataClient public interface MetadataClient extends IRestfulClient { @Metadata CapabilityStatement getServerMetadata(); // ....Other methods can also be added as usual.... } //END SNIPPET: metadataClient //START SNIPPET: historyClient public interface HistoryClient extends IBasicClient { /** Server level (history of ALL resources) */ @History Bundle getHistoryServer(); /** Type level (history of all resources of a given type) */ @History(type=Patient.class) Bundle getHistoryPatientType(); /** Instance level (history of a specific resource instance by type and ID) */ @History(type=Patient.class) Bundle getHistoryPatientInstance(@IdParam IdType theId); /** * Either (or both) of the "since" and "count" paramaters can * also be included in any of the methods above. */ @History Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count Integer theCount); } //END SNIPPET: historyClient public void bbbbb() throws DataFormatException, IOException { //START SNIPPET: metadataClientUsage FhirContext ctx = FhirContext.forDstu2(); MetadataClient client = ctx.newRestfulClient(MetadataClient.class, "http://spark.furore.com/fhir"); CapabilityStatement metadata = client.getServerMetadata(); System.out.println(ctx.newXmlParser().encodeResourceToString(metadata)); //END SNIPPET: metadataClientUsage } //START SNIPPET: readTags @Read() public Patient readPatient(@IdParam IdType theId) { Patient retVal = new Patient(); // ..populate demographics, contact, or anything else you usually would.. // Populate some tags retVal.getMeta().addTag("http://animals", "Dog", "Canine Patient"); // TODO: more realistic example retVal.getMeta().addTag("http://personality", "Friendly", "Friendly"); // TODO: more realistic example return retVal; } //END SNIPPET: readTags //START SNIPPET: clientReadInterface private interface IPatientClient extends IBasicClient { /** Read a patient from a server by ID */ @Read Patient readPatient(@IdParam IdType theId); // Only one method is shown here, but many methods may be // added to the same client interface! } //END SNIPPET: clientReadInterface public void clientRead() { //START SNIPPET: clientReadTags IPatientClient client = FhirContext.forDstu2().newRestfulClient(IPatientClient.class, "http://foo/fhir"); Patient patient = client.readPatient(new IdType("1234")); // Access the tag list List<Coding> tagList = patient.getMeta().getTag(); for (Coding next : tagList) { // ..process the tags somehow.. } //END SNIPPET: clientReadTags //START SNIPPET: clientCreateTags Patient newPatient = new Patient(); // Populate the resource object newPatient.addIdentifier().setUse(IdentifierUse.OFFICIAL).setValue("123"); newPatient.addName().setFamily("Jones").addGiven("Frank"); // Populate some tags newPatient.getMeta().addTag("http://animals", "Dog", "Canine Patient"); // TODO: more realistic example newPatient.getMeta().addTag("http://personality", "Friendly", "Friendly"); // TODO: more realistic example // ...invoke the create method on the client... //END SNIPPET: clientCreateTags } //START SNIPPET: createTags @Create public MethodOutcome createPatientResource(@ResourceParam Patient thePatient) { // ..save the resouce.. IdType id = new IdType("123"); // the new databse primary key for this resource // Get the tag list List<Coding> tags = thePatient.getMeta().getTag(); for (Coding tag : tags) { // process/save each tag somehow } return new MethodOutcome(id); } //END SNIPPET: createTags //START SNIPPET: tagMethodProvider public class TagMethodProvider { /** Return a list of all tags that exist on the server */ @GetTags public TagList getAllTagsOnServer() { return new TagList(); // populate this } /** Return a list of all tags that exist on at least one instance * of the given resource type */ @GetTags(type=Patient.class) public TagList getTagsForAllResourcesOfResourceType() { return new TagList(); // populate this } /** Return a list of all tags that exist on a specific instance * of the given resource type */ @GetTags(type=Patient.class) public TagList getTagsForResources(@IdParam IdType theId) { return new TagList(); // populate this } /** Return a list of all tags that exist on a specific version * of the given resource type */ @GetTags(type=Patient.class) public TagList getTagsForResourceVersion(@IdParam IdType theId) { return new TagList(); // populate this } /** Add tags to a resource */ @AddTags(type=Patient.class) public void getTagsForResourceVersion(@IdParam IdType theId, @TagListParam TagList theTagList) { // add tags } /** Add tags to a resource version */ @AddTags(type=Patient.class) public void addTagsToResourceVersion(@IdParam IdType theId, @TagListParam TagList theTagList) { // add tags } /** Remove tags from a resource */ @DeleteTags(type=Patient.class) public void deleteTagsFromResourceVersion(@IdParam IdType theId, @TagListParam TagList theTagList) { // add tags } } //END SNIPPET: tagMethodProvider //START SNIPPET: transaction @Transaction public Bundle transaction(@TransactionParam Bundle theInput) { for (BundleEntryComponent nextEntry : theInput.getEntry()) { // Process entry } Bundle retVal = new Bundle(); // Populate return bundle return retVal; } //END SNIPPET: transaction }