This commit is contained in:
Jens Kristian Villadsen 2016-11-09 10:36:08 +01:00
commit 0aaadc5228
2775 changed files with 1504847 additions and 1258784 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
.attach*
/bin
/target
target/
@ -16,6 +17,7 @@ tmp.txt
tmp.txt
ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString/
ca.uhn.fhir.jpa.entity.ResourceTable/
ca.uhn.fhir.jpa.entity.TermConcept/
# Vagrant stuff.
.vagrant
@ -123,6 +125,7 @@ tmp/
*~.nib
local.properties
.loadpath
*.hprof
# External tool builders
.externalToolBuilders/

View File

@ -19,7 +19,5 @@ before_script:
- export MAVEN_SKIP_RC=true
script:
# - mvn -e -B clean install && cd hapi-fhir-cobertura && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report
- mvn -e -B clean install && cd hapi-fhir-cobertura && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID -P COBERTURA clean cobertura:cobertura coveralls:report
# - mvn -e -B clean install && cd hapi-fhir-ra && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report
- mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -18,32 +18,37 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client-okhttp</artifactId>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
@ -54,7 +59,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>

View File

@ -1,16 +1,24 @@
package example;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
@ -87,4 +95,43 @@ public class AuthorizationInterceptors {
}
//END SNIPPET: patientAndAdmin
//START SNIPPET: conditionalUpdate
@Update()
public MethodOutcome update(
@IdParam IdDt theId,
@ResourceParam Patient theResource,
@ConditionalUrlParam String theConditionalUrl,
RequestDetails theRequestDetails) {
// If we're processing a conditional URL...
if (isNotBlank(theConditionalUrl)) {
// Pretend we've done the conditional processing. Now let's
// notify the interceptors that an update has been performed
// and supply the actual ID that's being updated
IdDt actual = new IdDt("Patient", "1123");
// There are a number of possible constructors for ActionRequestDetails.
// You should supply as much detail about the sub-operation as possible
IServerInterceptor.ActionRequestDetails subRequest =
new IServerInterceptor.ActionRequestDetails(theRequestDetails, actual);
// Notify the interceptors
subRequest.notifyIncomingRequestPreHandled(RestOperationTypeEnum.UPDATE);
}
// In a real server, perhaps we would process the conditional
// request differently and follow a separate path. Either way,
// let's pretend there is some storage code here.
theResource.setId(theId.withVersion("2"));
MethodOutcome retVal = new MethodOutcome();
retVal.setCreated(true);
retVal.setResource(theResource);
return retVal;
}
//END SNIPPET: conditionalUpdate
}

View File

@ -1,6 +1,7 @@
package example;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor;
@ -34,6 +35,19 @@ public class ClientExamples {
// END SNIPPET: proxy
}
@SuppressWarnings("unused")
public void createOkHttp() {
// START SNIPPET: okhttp
FhirContext ctx = FhirContext.forDstu3();
// Use OkHttp
ctx.setRestfulClientFactory(new OkHttpRestfulClientFactory(ctx));
// Create the client
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
// END SNIPPET: okhttp
}
@SuppressWarnings("unused")
public void createTimeouts() {
// START SNIPPET: timeouts

View File

@ -7,7 +7,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire.GroupQuestion;
import ca.uhn.fhir.model.dstu2.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
@ -17,7 +20,21 @@ public class ExtensionsDstu2 {
@SuppressWarnings("unused")
public static void main(String[] args) throws DataFormatException, IOException {
{
Questionnaire q= new Questionnaire();
GroupQuestion item = q.getGroup().addQuestion();
item.setText("Hello");
ExtensionDt extension = new ExtensionDt(false, "http://hl7.org/fhir/StructureDefinition/translation");
item.getTextElement().addUndeclaredExtension(extension);
extension.addUndeclaredExtension(new ExtensionDt(false, "lang", new CodeDt("es")));
extension.addUndeclaredExtension(new ExtensionDt(false, "cont", new StringDt("hola")));
System.out.println(FhirContext.forDstu2().newJsonParser().setPrettyPrint(true).encodeResourceToString(q));
}
// START SNIPPET: resourceExtension
// Create an example patient
Patient patient = new Patient();

View File

@ -3,9 +3,11 @@ package example;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.PerformanceOptionsEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
@ -25,13 +27,39 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.method.SearchStyleEnum;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
public class GenericClientExample {
public static void deferModelScanning() {
// START SNIPPET: deferModelScanning
// Create a context and configure it for deferred child scanning
FhirContext ctx = FhirContext.forDstu2();
ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING);
// Now create a client and use it
String serverBase = "http://fhirtest.uhn.ca/baseDstu2";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// END SNIPPET: deferModelScanning
}
public static void performance() {
// START SNIPPET: dontValidate
// Create a context
FhirContext ctx = FhirContext.forDstu2();
// Disable server validation (don't pull the server's metadata first)
ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
// Now create a client and use it
String serverBase = "http://fhirtest.uhn.ca/baseDstu2";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// END SNIPPET: dontValidate
}
public static void simpleExample() {
// START SNIPPET: simple
// We're connecting to a DSTU1 compliant server in this example
@ -207,7 +235,7 @@ public class GenericClientExample {
}
{
// START SNIPPET: delete
BaseOperationOutcome resp = client.delete().resourceById(new IdDt("Patient", "1234")).execute();
IBaseOperationOutcome resp = client.delete().resourceById(new IdDt("Patient", "1234")).execute();
// outcome may be null if the server didn't return one
if (resp != null) {

View File

@ -0,0 +1,42 @@
package example;
import java.io.IOException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
public class Parser {
public static void main(String[] args) throws DataFormatException, IOException {
{
//START SNIPPET: disableStripVersions
FhirContext ctx = FhirContext.forDstu2();
IParser parser = ctx.newJsonParser();
// Disable the automatic stripping of versions from references on the parser
parser.setStripVersionsFromReferences(false);
//END SNIPPET: disableStripVersions
//START SNIPPET: disableStripVersionsCtx
ctx.getParserOptions().setStripVersionsFromReferences(false);
//END SNIPPET: disableStripVersionsCtx
}
{
//START SNIPPET: disableStripVersionsField
FhirContext ctx = FhirContext.forDstu2();
IParser parser = ctx.newJsonParser();
// Preserve versions only on these two fields (for the given parser)
parser.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference", "Patient.managingOrganization");
// You can also apply this setting to the context so that it will
// flow to all parsers
ctx.getParserOptions().setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference", "Patient.managingOrganization");
//END SNIPPET: disableStripVersionsField
}
}
}

View File

@ -10,6 +10,8 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hl7.fhir.dstu3.model.IdType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
@ -34,6 +36,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
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;
@ -107,6 +110,23 @@ public List<Organization> getAllOrganizations() {
}
//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
@ -363,7 +383,11 @@ public void deletePatientConditional(@IdParam IdDt theId, @ConditionalUrlParam S
//START SNIPPET: history
@History()
public List<Patient> getPatientHistory(@IdParam IdDt theId) {
public List<Patient> getPatientHistory(
@IdParam IdDt theId,
@Since InstantDt theSince,
@At DateRangeParam theAt
) {
List<Patient> retVal = new ArrayList<Patient>();
Patient patient = new Patient();
@ -593,7 +617,7 @@ public List<DiagnosticReport> getDiagnosticReport(
@RequiredParam(name=DiagnosticReport.SP_IDENTIFIER)
TokenParam theIdentifier,
@IncludeParam(allow= {"DiagnosticReport.subject"})
@IncludeParam(allow= {"DiagnosticReport:subject"})
Set<Include> theIncludes ) {
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
@ -602,7 +626,7 @@ public List<DiagnosticReport> getDiagnosticReport(
DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier);
// If the client has asked for the subject to be included:
if (theIncludes.contains(new Include("DiagnosticReport.subject"))) {
if (theIncludes.contains(new Include("DiagnosticReport:subject"))) {
// The resource reference should contain the ID of the patient
IdDt subjectId = report.getSubject().getReference();
@ -641,7 +665,7 @@ public List<DiagnosticReport> getDiagnosticReport(
@RequiredParam(name=DiagnosticReport.SP_IDENTIFIER)
TokenParam theIdentifier,
@IncludeParam(allow= {"DiagnosticReport.subject"})
@IncludeParam(allow= {"DiagnosticReport:subject"})
String theInclude ) {
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
@ -650,7 +674,7 @@ public List<DiagnosticReport> getDiagnosticReport(
DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier);
// If the client has asked for the subject to be included:
if ("DiagnosticReport.subject".equals(theInclude)) {
if ("DiagnosticReport:subject".equals(theInclude)) {
// The resource reference should contain the ID of the patient
IdDt subjectId = report.getSubject().getReference();

View File

@ -53,4 +53,14 @@ public class BasicSecurityInterceptor extends InterceptorAdapter
}
//END SNIPPET: basicAuthInterceptor
public void basicAuthInterceptorRealm() {
//START SNIPPET: basicAuthInterceptorRealm
AuthenticationException ex = new AuthenticationException();
ex.addAuthenticateHeaderForRealm("myRealm");
throw ex;
//END SNIPPET: basicAuthInterceptorRealm
}
}

View File

@ -0,0 +1,105 @@
package example;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.PrePopulatedValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
public class ValidateDirectory {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidateDirectory.class);
public static void main(String[] args) throws Exception {
// Load all profiles in this directory
File profileDirectory = new File("/tmp/directory/with/profiles");
// Validate resources in this directory
File resourceDirectory = new File("/tmp/directory/with/resources/to/validate");
FhirContext ctx = FhirContext.forDstu3();
IParser xmlParser = ctx.newXmlParser();
IParser jsonParser = ctx.newJsonParser();
Map<String, StructureDefinition> structureDefinitions = new HashMap<String, StructureDefinition>();
Map<String, CodeSystem> codeSystems = new HashMap<String, CodeSystem>();
Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>();
// Load all profile files
for (File nextFile : profileDirectory.listFiles()) {
IBaseResource parsedRes = null;
if (nextFile.getAbsolutePath().toLowerCase().endsWith(".xml")) {
parsedRes = xmlParser.parseResource(new FileReader(nextFile));
} else if (nextFile.getAbsolutePath().toLowerCase().endsWith(".json")) {
parsedRes = jsonParser.parseResource(new FileReader(nextFile));
} else {
ourLog.info("Ignoring file: {}", nextFile.getName());
}
if (parsedRes instanceof StructureDefinition) {
StructureDefinition res = (StructureDefinition) parsedRes;
if (isNotBlank(res.getUrl())) {
structureDefinitions.put(res.getUrl(), res);
}
} else if (parsedRes instanceof ValueSet) {
ValueSet res = (ValueSet) parsedRes;
if (isNotBlank(res.getUrl())) {
valueSets.put(res.getUrl(), res);
}
} else if (parsedRes instanceof CodeSystem) {
CodeSystem res = (CodeSystem) parsedRes;
if (isNotBlank(res.getUrl())) {
codeSystems.put(res.getUrl(), res);
}
}
}
FhirInstanceValidator instanceValidator = new FhirInstanceValidator();
ValidationSupportChain validationSupportChain = new ValidationSupportChain();
validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport());
validationSupportChain.addValidationSupport(new PrePopulatedValidationSupport(structureDefinitions, valueSets, codeSystems));
instanceValidator.setValidationSupport(validationSupportChain);
FhirValidator val = ctx.newValidator();
val.registerValidatorModule(instanceValidator);
// Loop through the files in the validation directory and validate each one
for (File nextFile : resourceDirectory.listFiles()) {
if (nextFile.getAbsolutePath().toLowerCase().endsWith(".xml")) {
ourLog.info("Going to validate: {}", nextFile.getName());
} else if (nextFile.getAbsolutePath().toLowerCase().endsWith(".json")) {
ourLog.info("Going to validate: {}", nextFile.getName());
} else {
ourLog.info("Ignoring file: {}", nextFile.getName());
continue;
}
String input = IOUtils.toString(new FileReader(nextFile));
ValidationResult result = val.validateWithResult(input);
IBaseOperationOutcome oo = result.toOperationOutcome();
ourLog.info("Result:\n{}", xmlParser.setPrettyPrint(true).encodeResourceToString(oo));
}
}
}

View File

@ -289,5 +289,5 @@ public class ValidatorExamples {
// END SNIPPET: validateFiles
}
}

View File

@ -0,0 +1,50 @@
package example;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.validation.FhirValidator;
public class ValidatorExamplesDstu3 {
public void validateProfileDstu3() {
// START SNIPPET: validateFiles
FhirContext ctx = FhirContext.forDstu3();
FhirValidator validator = ctx.newValidator();
// Typically if you are doing profile validation, you want to disable
// the schema/schematron validation since the profile will specify
// all the same rules (and more)
validator.setValidateAgainstStandardSchema(false);
validator.setValidateAgainstStandardSchematron(false);
// FhirInstanceValidator is the validation module that handles
// profile validation. So, create an InstanceValidator module
// and register it to the validator.
FhirInstanceValidator instanceVal = new FhirInstanceValidator();
validator.registerValidatorModule(instanceVal);
// FhirInstanceValidator requires an instance of "IValidationSupport" in
// order to function. This module is used by the validator to actually obtain
// all of the resources it needs in order to perform validation. Specifically,
// the validator uses it to fetch StructureDefinitions, ValueSets, CodeSystems,
// etc, as well as to perform terminology validation.
//
// The implementation used here (ValidationSupportChain) is allows for
// multiple implementations to be used in a chain, where if a specific resource
// is needed the whole chain is tried and the first module which is actually
// able to answer is used. The first entry in the chain that we register is
// the DefaultProfileValidationSupport, which supplies the "built-in" FHIR
// StructureDefinitions and ValueSets
ValidationSupportChain validationSupportChain = new ValidationSupportChain();
validationSupportChain.addValidationSupport(new DefaultProfileValidationSupport());
instanceVal.setValidationSupport(validationSupportChain);
// END SNIPPET: validateFiles
}
}

View File

@ -0,0 +1,84 @@
package example.customtype;
import org.hl7.fhir.dstu3.model.BackboneElement;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.util.ElementUtil;
//START SNIPPET: resource
@ResourceDef(name = "Patient")
public class CustomCompositeExtension extends Patient {
private static final long serialVersionUID = 1L;
/**
* A custom extension
*/
@Child(name = "foo")
@Extension(url="http://acme.org/fooParent", definedLocally = false, isModifier = false)
protected FooParentExtension fooParentExtension;
public FooParentExtension getFooParentExtension() {
return fooParentExtension;
}
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(fooParentExtension);
}
public void setFooParentExtension(FooParentExtension theFooParentExtension) {
fooParentExtension = theFooParentExtension;
}
@Block
public static class FooParentExtension extends BackboneElement {
private static final long serialVersionUID = 4522090347756045145L;
@Child(name = "childA")
@Extension(url = "http://acme.org/fooChildA", definedLocally = false, isModifier = false)
private StringType myChildA;
@Child(name = "childB")
@Extension(url = "http://acme.org/fooChildB", definedLocally = false, isModifier = false)
private StringType myChildB;
@Override
public FooParentExtension copy() {
FooParentExtension copy = new FooParentExtension();
copy.myChildA = myChildA;
copy.myChildB = myChildB;
return copy;
}
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(myChildA, myChildB);
}
public StringType getChildA() {
return myChildA;
}
public StringType getChildB() {
return myChildB;
}
public void setChildA(StringType theChildA) {
myChildA = theChildA;
}
public void setChildB(StringType theChildB) {
myChildB = theChildB;
}
}
}
//END SNIPPET: resource

View File

@ -0,0 +1,63 @@
package example.customtype;
//START SNIPPET: datatype
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Type;
import org.hl7.fhir.instance.model.api.ICompositeType;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.util.ElementUtil;
/**
* This is an example of a custom datatype.
*
* This is an STU3 example so it extends Type and implements ICompositeType. For
* DSTU2 it would extend BaseIdentifiableElement and implement ICompositeDatatype.
*/
@DatatypeDef(name="CustomDatatype")
public class CustomDatatype extends Type implements ICompositeType {
private static final long serialVersionUID = 1L;
@Child(name = "date", order = 0, min = 1, max = 1)
private DateTimeType myDate;
@Child(name = "kittens", order = 1, min = 1, max = 1)
private StringType myKittens;
public DateTimeType getDate() {
if (myDate == null)
myDate = new DateTimeType();
return myDate;
}
public StringType getKittens() {
return myKittens;
}
@Override
public boolean isEmpty() {
return ElementUtil.isEmpty(myDate, myKittens);
}
public CustomDatatype setDate(DateTimeType theValue) {
myDate = theValue;
return this;
}
public CustomDatatype setKittens(StringType theKittens) {
myKittens = theKittens;
return this;
}
@Override
protected CustomDatatype typedCopy() {
CustomDatatype retVal = new CustomDatatype();
super.copyValues(retVal);
retVal.myDate = myDate;
return retVal;
}
}
//END SNIPPET: datatype

View File

@ -0,0 +1,86 @@
package example.customtype;
// START SNIPPET: resource
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.dstu3.model.DomainResource;
import org.hl7.fhir.dstu3.model.ResourceType;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.Type;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.util.ElementUtil;
/**
* This is an example of a custom resource that also uses a custom
* datatype.
*
* Note that we are extensing DomainResource for an STU3
* resource. For DSTU2 it would be BaseResource.
*/
@ResourceDef(name = "CustomResource", profile = "http://hl7.org/fhir/profiles/custom-resource")
public class CustomResource extends DomainResource {
private static final long serialVersionUID = 1L;
/**
* We give the resource a field with name "television". This field has no
* specific type, so it's a choice[x] field for any type.
*/
@Child(name="television", min=1, max=Child.MAX_UNLIMITED, order=0)
private List<Type> myTelevision;
/**
* We'll give it one more field called "dogs"
*/
@Child(name = "dogs", min=0, max=1, order=1)
private StringType myDogs;
@Override
public CustomResource copy() {
CustomResource retVal = new CustomResource();
super.copyValues(retVal);
retVal.myTelevision = myTelevision;
retVal.myDogs = myDogs;
return retVal;
}
public List<Type> getTelevision() {
if (myTelevision == null) {
myTelevision = new ArrayList<Type>();
}
return myTelevision;
}
public StringType getDogs() {
return myDogs;
}
@Override
public ResourceType getResourceType() {
return null;
}
@Override
public FhirVersionEnum getStructureFhirVersionEnum() {
return FhirVersionEnum.DSTU3;
}
@Override
public boolean isEmpty() {
return ElementUtil.isEmpty(myTelevision, myDogs);
}
public void setTelevision(List<Type> theValue) {
this.myTelevision = theValue;
}
public void setDogs(StringType theDogs) {
myDogs = theDogs;
}
}
// END SNIPPET: resource

View File

@ -0,0 +1,44 @@
package example.customtype;
import java.util.Date;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.StringType;
import ca.uhn.fhir.context.FhirContext;
public class CustomUsage {
public static void main(String[] args) {
// START SNIPPET: usage
// Create a context. Note that we declare the custom types we'll be using
// on the context before actually using them
FhirContext ctx = FhirContext.forDstu3();
ctx.registerCustomType(CustomResource.class);
ctx.registerCustomType(CustomDatatype.class);
// Now let's create an instance of our custom resource type
// and populate it with some data
CustomResource res = new CustomResource();
// Add some values, including our custom datatype
DateType value0 = new DateType("2015-01-01");
res.getTelevision().add(value0);
CustomDatatype value1 = new CustomDatatype();
value1.setDate(new DateTimeType(new Date()));
value1.setKittens(new StringType("FOO"));
res.getTelevision().add(value1);
res.setDogs(new StringType("Some Dogs"));
// Now let's serialize our instance
String output = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res);
System.out.println(output);
// END SNIPPET: usage
}
}

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -26,22 +26,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<!--
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</exclusion>
-->
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
@ -51,24 +40,36 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-android</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-client-okhttp</artifactId>
<version>2.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@ -82,27 +83,10 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Android does not come with the Servlet API bundled, and MethodUtil
requires it.
We provide a dummy implementation of servlet api to reduce size
and prevent from rewriting the BaseMethodBinding and friends.
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
@ -118,23 +102,59 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<classpathDependencyScopeExclude>compile+runtime+test+provided</classpathDependencyScopeExclude>
<additionalClasspathElements>
<additionalClasspathElement>${project.build.directory}/hapi-fhir-android-${project.version}-dstu2.jar</additionalClasspathElement>
</additionalClasspathElements>
<classpathDependencyExcludes>
<classpathDependencyExclude>ca.uhn.hapi.fhir:hapi-fhir-base</classpathDependencyExclude>
<classpathDependencyExclude>org.codehaus.woodstox:*</classpathDependencyExclude>
<classpathDependencyExclude>javax.json:*</classpathDependencyExclude>
<classpathDependencyExclude>org.glassfish:javax.json</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
<executions>
<execution>
<id>dstu2_shade</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<includes>
<include>**/*Dstu2ShadeIT.java</include>
</includes>
<additionalClasspathElements>
<additionalClasspathElement>${basedir}/target/hapi-fhir-android-${project.version}-dstu2.jar</additionalClasspathElement>
</additionalClasspathElements>
<classpathDependencyExcludes>
<classpathDependencyExclude>ca.uhn.hapi.fhir:*</classpathDependencyExclude>
<classpathDependencyExclude>org.codehaus.woodstox:woodstox-core-asl</classpathDependencyExclude>
<classpathDependencyExclude>org.codehaus.woodstox:stax2-api</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</execution>
<execution>
<id>dstu2</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<includes>
<include>**/*Dstu2IT.java</include>
</includes>
<classpathDependencyExcludes>
<classpathDependencyExclude>org.codehaus.woodstox:woodstox-core-asl</classpathDependencyExclude>
<classpathDependencyExclude>org.codehaus.woodstox:stax2-api</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</execution>
<execution>
<id>dstu3</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<includes>
<include>**/*Dstu3IT.java</include>
</includes>
<classpathDependencyExcludes>
<classpathDependencyExclude>org.codehaus.woodstox:woodstox-core-asl</classpathDependencyExclude>
<classpathDependencyExclude>org.codehaus.woodstox:stax2-api</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</execution>
</executions>
</plugin>
@ -148,18 +168,10 @@
<artifactSet>
<includes combine.children="append">
<include>ca.uhn.hapi.fhir:hapi-fhir-base</include>
<include>org.glassfish:javax.json</include>
<include>ca.uhn.hapi.fhir:hapi-fhir-client-okhttp</include>
<include>org.codehaus.woodstox:woodstox-core-asl</include>
<include>javax.xml.stream:stax-api</include>
<include>org.codehaus.woodstox:stax2-api</include>
<include>org.glassfish:javax.json</include>
<!--
<include>net.sourceforge.cobertura:cobertura</include>
<include>org.apache.commons:*</include>
<include>org.apache.httpcomponents:*</include>
<include>commons-codec:commons-codec</include>
-->
<include>javax.servlet:javax.servlet-api</include>
</includes>
</artifactSet>
<relocations>
@ -171,6 +183,10 @@
<pattern>javax.json</pattern>
<shadedPattern>ca.uhn.fhir.repackage.javax.json</shadedPattern>
</relocation>
<relocation>
<pattern>com.ctc.wstx.stax</pattern>
<shadedPattern>ca.uhn.fhir.repackage.com.ctc.wstx.stax</shadedPattern>
</relocation>
</relocations>
<filters combine.children="append">
<!-- Exclude server side stuff, except exceptions which are used clientside -->
@ -212,8 +228,8 @@
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</goals>
</execution>
<!-- dstu jar -->
<execution>
<id>dstu</id>
@ -291,6 +307,27 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<classFolders>
<classFolder>${basedir}/target/classes</classFolder>
</classFolders>
<sourceFolders>
<sourceFolder>${basedir}/src/main/java</sourceFolder>
</sourceFolders>
<dumpOnExit>true</dumpOnExit>
</configuration>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@ -1,14 +0,0 @@
package ca.uhn.fhir.android;
import ca.uhn.fhir.context.FhirContext;
public class AndroidLoader {
public static void main(String[] theArgs) {
FhirContext ctx = FhirContext.forDstu2();
ctx.newJsonParser();
ctx.newXmlParser();
ctx.newRestfulGenericClient("");
}
}

View File

@ -0,0 +1,16 @@
package ca.uhn.fhir.android;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory;
/**
* This class exists in order to ensure that
*/
public class AndroidMarker {
public static void configureContext(FhirContext theContext) {
theContext.setRestfulClientFactory(new OkHttpRestfulClientFactory(theContext));
}
}

View File

@ -0,0 +1,192 @@
package ca.uhn.fhir.android;
import static org.junit.Assert.*;
import java.io.File;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.naming.ConfigurationException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
public class BuiltJarDstu2IT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarDstu2IT.class);
@BeforeClass
public static void beforeClass() {
System.setProperty("javax.xml.stream.XMLInputFactory", "FOO");
System.setProperty("javax.xml.stream.XMLOutputFactory", "FOO");
}
@Test
public void testParserXml() throws Exception {
FhirContext ctx = FhirContext.forDstu2();
Patient p = new Patient();
p.addIdentifier().setSystem("system");
try {
ctx.newXmlParser().encodeResourceToString(p);
fail();
} catch (ca.uhn.fhir.context.ConfigurationException e) {
assertEquals("Unable to initialize StAX - XML processing is disabled",e.getMessage());
}
}
@Test
public void testParserJson() {
FhirContext ctx = FhirContext.forDstu2();
Observation o = new Observation();
o.getCode().setText("TEXT");
o.setValue(new QuantityDt(123));
o.addIdentifier().setSystem("system");
String str = ctx.newJsonParser().encodeResourceToString(o);
Observation p2 = ctx.newJsonParser().parseResource(Observation.class, str);
assertEquals("TEXT", p2.getCode().getText());
QuantityDt dt = (QuantityDt) p2.getValue();
dt.getComparatorElement().getValueAsEnum();
}
/**
* A simple client test - We try to connect to a server that doesn't exist, but
* if we at least get the right exception it means we made it up to the HTTP/network stack
*
* Disabled for now - TODO: add the old version of the apache client (the one that
* android uses) and see if this passes
*/
@SuppressWarnings("deprecation")
public void testClient() {
FhirContext ctx = FhirContext.forDstu2();
try {
IGenericClient client = ctx.newRestfulGenericClient("http://127.0.0.1:44442/SomeBase");
client.conformance();
} catch (FhirClientConnectionException e) {
// this is good
}
}
/**
* Android does not like duplicate entries in the JAR
*/
@Test
public void testJarContents() throws Exception {
String wildcard = "hapi-fhir-android-*.jar";
Collection<File> files = FileUtils.listFiles(new File("target"), new WildcardFileFilter(wildcard), null);
if (files.isEmpty()) {
throw new Exception("No files matching " + wildcard);
}
for (File file : files) {
if (file.getName().endsWith("sources.jar")) {
continue;
}
if (file.getName().endsWith("javadoc.jar")) {
continue;
}
if (file.getName().contains("original.jar")) {
continue;
}
ourLog.info("Testing file: {}", file);
ZipFile zip = new ZipFile(file);
int totalClasses = 0;
int totalMethods = 0;
TreeSet<ClassMethodCount> topMethods = new TreeSet<ClassMethodCount>();
try {
Set<String> names = new HashSet<String>();
for (Enumeration<? extends ZipEntry> iter = zip.entries(); iter.hasMoreElements();) {
ZipEntry next = iter.nextElement();
String nextName = next.getName();
if (!names.add(nextName)) {
throw new Exception("File " + file + " contains duplicate contents: " + nextName);
}
if (nextName.contains("$") == false) {
if (nextName.endsWith(".class")) {
String className = nextName.replace("/", ".").replace(".class", "");
try {
Class<?> clazz = Class.forName(className);
int methodCount = clazz.getMethods().length;
topMethods.add(new ClassMethodCount(className, methodCount));
totalClasses++;
totalMethods += methodCount;
} catch (NoClassDefFoundError e) {
// ignore
} catch (ClassNotFoundException e) {
// ignore
}
}
}
}
ourLog.info("File {} contains {} entries", file, names.size());
ourLog.info("Total classes {} - Total methods {}", totalClasses, totalMethods);
ourLog.info("Top classes {}", new ArrayList<ClassMethodCount>(topMethods).subList(Math.max(0, topMethods.size() - 10), topMethods.size()));
} finally {
zip.close();
}
}
}
private static class ClassMethodCount implements Comparable<ClassMethodCount> {
private String myClassName;
private int myMethodCount;
public ClassMethodCount(String theClassName, int theMethodCount) {
myClassName = theClassName;
myMethodCount = theMethodCount;
}
@Override
public String toString() {
return myClassName + "[" + myMethodCount + "]";
}
@Override
public int compareTo(ClassMethodCount theO) {
return myMethodCount - theO.myMethodCount;
}
public String getClassName() {
return myClassName;
}
public void setClassName(String theClassName) {
myClassName = theClassName;
}
public int getMethodCount() {
return myMethodCount;
}
public void setMethodCount(int theMethodCount) {
myMethodCount = theMethodCount;
}
}
}

View File

@ -1,15 +1,9 @@
package ca.uhn.fhir.android;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Observable;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@ -18,61 +12,48 @@ import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.junit.BeforeClass;
import org.junit.Test;
import com.ctc.wstx.stax.WstxInputFactory;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
public class BuiltJarIT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarIT.class);
public class BuiltJarDstu2ShadeIT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarDstu2ShadeIT.class);
@BeforeClass
public static void beforeClass() {
System.setProperty("javax.xml.stream.XMLInputFactory", "com.ctc.wstx.stax.WstxInputFactory");
System.setProperty("javax.xml.stream.XMLOutputFactory", "com.ctc.wstx.stax.WstxOutputFactory");
}
@Test
public void testParserXml() throws Exception {
// fail("*******: " + WstxInputFactory.class.getProtectionDomain().getCodeSource().getLocation().toString());
FhirContext ctx = FhirContext.forDstu2();
Patient p = new Patient();
p.addIdentifier().setSystem("system");
String str = ctx.newXmlParser().encodeResourceToString(p);
Patient p2 = ctx.newXmlParser().parseResource(Patient.class, str);
assertEquals("system", p2.getIdentifierFirstRep().getSystemElement().getValueAsString());
}
@Test
public void testParserJson() {
ourLog.info("AAAAA");
ourLog.info("AAAAA");
FhirContext ctx = FhirContext.forDstu2();
Observation o = new Observation();
o.getCode().setText("TEXT");
o.setValue(new QuantityDt(123));
o.addIdentifier().setSystem("system");
String str = ctx.newJsonParser().encodeResourceToString(o);
Observation p2 = ctx.newJsonParser().parseResource(Observation.class, str);
assertEquals("TEXT", p2.getCode().getText());
QuantityDt dt = (QuantityDt) p2.getValue();
dt.getComparatorElement().getValueAsEnum();
QuantityCompararatorEnum.GREATERTHAN.name();
}
/**
@ -91,7 +72,7 @@ public class BuiltJarIT {
// this is good
}
}
/**
* Android does not like duplicate entries in the JAR
*/
@ -113,15 +94,15 @@ public class BuiltJarIT {
if (file.getName().contains("original.jar")) {
continue;
}
ourLog.info("Testing file: {}", file);
ZipFile zip = new ZipFile(file);
int totalClasses = 0;
int totalMethods = 0;
TreeSet<ClassMethodCount> topMethods = new TreeSet<ClassMethodCount>();
try {
Set<String> names = new HashSet<String>();
for (Enumeration<? extends ZipEntry> iter = zip.entries(); iter.hasMoreElements();) {
@ -130,16 +111,16 @@ public class BuiltJarIT {
if (!names.add(nextName)) {
throw new Exception("File " + file + " contains duplicate contents: " + nextName);
}
if (nextName.contains("$") == false) {
if (nextName.endsWith(".class")) {
String className = nextName.replace("/", ".").replace(".class", "");
try {
Class<?> clazz = Class.forName(className);
int methodCount = clazz.getMethods().length;
topMethods.add(new ClassMethodCount(className, methodCount));
totalClasses++;
totalMethods += methodCount;
Class<?> clazz = Class.forName(className);
int methodCount = clazz.getMethods().length;
topMethods.add(new ClassMethodCount(className, methodCount));
totalClasses++;
totalMethods += methodCount;
} catch (NoClassDefFoundError e) {
// ignore
} catch (ClassNotFoundException e) {
@ -148,11 +129,11 @@ public class BuiltJarIT {
}
}
}
ourLog.info("File {} contains {} entries", file, names.size());
ourLog.info("Total classes {} - Total methods {}", totalClasses, totalMethods);
ourLog.info("Top classes {}", new ArrayList<ClassMethodCount>(topMethods).subList(Math.max(0,topMethods.size() - 10), topMethods.size()));
ourLog.info("Top classes {}", new ArrayList<ClassMethodCount>(topMethods).subList(Math.max(0, topMethods.size() - 10), topMethods.size()));
} finally {
zip.close();
}
@ -163,7 +144,7 @@ public class BuiltJarIT {
private String myClassName;
private int myMethodCount;
public ClassMethodCount(String theClassName, int theMethodCount) {
myClassName = theClassName;
myMethodCount = theMethodCount;
@ -194,7 +175,7 @@ public class BuiltJarIT {
public void setMethodCount(int theMethodCount) {
myMethodCount = theMethodCount;
}
}
}

View File

@ -0,0 +1,967 @@
package ca.uhn.fhir.android.client;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.Date;
import org.apache.http.client.ClientProtocolException;
import org.hl7.fhir.dstu3.model.Binary;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.VersionUtil;
import okhttp3.*;
import okio.Buffer;
public class GenericClientDstu3IT {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu3IT.class);
private int myAnswerCount;
private Call.Factory myHttpClient;
private ArgumentCaptor<Request> capt;
private Response myHttpResponse;
private Request myRequest;
private Protocol myProtocol;
@Before
public void before() throws IOException {
myHttpClient = mock(Call.Factory.class, Mockito.RETURNS_DEEP_STUBS);
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
Call httpResponse = mock(Call.class, Mockito.RETURNS_DEEP_STUBS);
capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(httpResponse);
myRequest = new Request.Builder().url("http://127.0.0.1").build();
myProtocol = Protocol.HTTP_1_1;
when(httpResponse.execute()).thenAnswer(new Answer<Response>() {
@Override
public Response answer(InvocationOnMock theInvocation) throws Throwable {
myAnswerCount++;
return myHttpResponse;
}});
myAnswerCount = 0;
}
private String expectedUserAgent() {
return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR " + FhirVersionEnum.DSTU3.getFhirVersionString() + "/DSTU3; okhttp/3.4.1)";
}
private String extractBodyAsString(ArgumentCaptor<Request> capt) throws IOException {
Buffer sink = new Buffer();
capt.getValue().body().writeTo(sink);
return new String(sink.readByteArray(), "UTF-8");
}
private void validateUserAgent(ArgumentCaptor<Request> capt) {
assertEquals(expectedUserAgent(), capt.getAllValues().get(0).header("User-Agent"));
}
/**
* TODO: narratives don't work without stax
*/
@Test
public void testBinaryCreateWithFhirContentType() throws Exception {
IParser p = ourCtx.newXmlParser();
OperationOutcome conf = new OperationOutcome();
conf.getText().setDivAsString("OK!");
String respString = p.encodeResourceToString(conf);
myHttpResponse = new Response.Builder()
.request(myRequest)
.protocol(myProtocol)
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_XML + "; charset=UTF-8"), respString))
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Patient pt = new Patient();
pt.getText().setDivAsString("A PATIENT");
Binary bin = new Binary();
bin.setContent(ourCtx.newJsonParser().encodeResourceToString(pt).getBytes("UTF-8"));
bin.setContentType(Constants.CT_FHIR_JSON);
client.create().resource(bin).execute();
Request request = capt.getAllValues().get(0);
ourLog.info(request.headers().toString());
assertEquals("http://example.com/fhir/Binary", request.url().toString());
validateUserAgent(capt);
assertEquals(Constants.CT_FHIR_XML_NEW + ";charset=utf-8", request.body().contentType().toString().toLowerCase().replace(" ", ""));
assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, request.header("Accept"));
Binary output = ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt));
assertEquals(Constants.CT_FHIR_JSON, output.getContentType());
Patient outputPt = (Patient) ourCtx.newJsonParser().parseResource(new String(output.getContent(), "UTF-8"));
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">A PATIENT</div>", outputPt.getText().getDivAsString());
}
/**
* See #150
*/
@Test
public void testNullAndEmptyParamValuesAreIgnored() throws Exception {
ArgumentCaptor<Request> capt = prepareClientForSearchResponse();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
client
.search()
.forResource(Patient.class)
.where(Patient.FAMILY.matches().value((String)null))
.and(Patient.BIRTHDATE.exactly().day((Date)null))
.and(Patient.GENDER.exactly().code((String)null))
.and(Patient.ORGANIZATION.hasId((String)null))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).url().toString());
idx++;
}
/**
* TODO: narratives don't work without stax
*/
@Test
public void testBinaryCreateWithNoContentType() throws Exception {
IParser p = ourCtx.newJsonParser();
OperationOutcome conf = new OperationOutcome();
conf.getText().setDivAsString("OK!");
final String respString = p.encodeResourceToString(conf);
myHttpResponse = new Response.Builder()
.request(myRequest)
.protocol(myProtocol)
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"), respString))
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Binary bin = new Binary();
bin.setContent(new byte[] { 0, 1, 2, 3, 4 });
client.create().resource(bin).execute();
Request request = capt.getAllValues().get(0);
ourLog.info(request.headers().toString());
assertEquals("http://example.com/fhir/Binary", request.url().toString());
validateUserAgent(capt);
assertEquals(Constants.CT_FHIR_XML_NEW + ";charset=utf-8", request.body().contentType().toString().toLowerCase().replace(" ", ""));
assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, request.header("Accept"));
assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent());
}
@SuppressWarnings("unchecked")
@Test
public void testClientFailures() throws Exception {
ResponseBody body = mock(ResponseBody.class);
when(body.source()).thenThrow(IllegalStateException.class, RuntimeException.class, Exception.class);
myHttpResponse = new Response.Builder()
.request(myRequest)
.protocol(myProtocol)
.code(200)
.body(body)
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (FhirClientConnectionException e) {
assertEquals("java.lang.IllegalStateException", e.getMessage());
}
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (RuntimeException e) {
assertEquals("java.lang.RuntimeException", e.toString());
}
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (FhirClientConnectionException e) {
assertEquals("java.lang.Exception", e.getMessage());
}
}
/**
* TODO: narratives don't work without stax
*/
@Test
public void testCreateWithPreferRepresentationServerReturnsResource() throws Exception {
final IParser p = ourCtx.newJsonParser();
final Patient resp1 = new Patient();
resp1.getText().setDivAsString("FINAL VALUE");
String respString = p.encodeResourceToString(resp1);
myHttpResponse = new Response.Builder()
.request(myRequest)
.protocol(myProtocol)
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"), respString))
.headers(Headers.of(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3"))
.build();
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Patient pt = new Patient();
pt.getText().setDivAsString("A PATIENT");
MethodOutcome outcome = client.create().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute();
assertNull(outcome.getOperationOutcome());
assertNotNull(outcome.getResource());
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">FINAL VALUE</div>", ((Patient) outcome.getResource()).getText().getDivAsString());
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).url().toString());
}
private ArgumentCaptor<Request> prepareClientForSearchResponse() throws IOException, ClientProtocolException {
final String respString = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
myHttpResponse = new Response.Builder()
.request(myRequest)
.protocol(myProtocol)
.code(200)
.body(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_JSON + "; charset=UTF-8"), respString))
.headers(Headers.of(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3"))
.build();
return capt;
}
/*
@Test
public void testForceConformance() throws Exception {
final IParser p = ourCtx.newJsonParser();
final Conformance conf = new Conformance();
conf.setCopyright("COPY");
final Patient patient = new Patient();
patient.addName().addFamily("FAM");
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
when(myHttpResponse.execute().body()).thenReturn(ResponseBody.create(MediaType.parse(Constants.CT_FHIR_XML + "; charset=UTF-8"), respString));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
private int myCount = 0;
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
final String respString;
if (myCount == 1 || myCount == 2) {
ourLog.info("Encoding patient");
respString = p.encodeResourceToString(patient);
} else {
ourLog.info("Encoding conformance");
respString = p.encodeResourceToString(conf);
}
myCount++;
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
IGenericClient client = ourCtx.newRestfulGenericClient("http://testForceConformance.com/fhir");
client.read().resource("Patient").withId("1").execute();
assertEquals(2, capt.getAllValues().size());
assertEquals("http://testForceConformance.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
assertEquals("http://testForceConformance.com/fhir/Patient/1?_format=json", capt.getAllValues().get(1).getURI().toASCIIString());
client.read().resource("Patient").withId("1").execute();
assertEquals(3, capt.getAllValues().size());
assertEquals("http://testForceConformance.com/fhir/Patient/1?_format=json", capt.getAllValues().get(2).getURI().toASCIIString());
client.forceConformanceCheck();
assertEquals(4, capt.getAllValues().size());
assertEquals("http://testForceConformance.com/fhir/metadata?_format=json", capt.getAllValues().get(3).getURI().toASCIIString());
}
@Test
public void testHttp499() throws Exception {
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 499, "Wacky Message"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader("HELLO"), StandardCharsets.UTF_8);
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (UnclassifiedServerFailureException e) {
assertEquals("ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException: HTTP 499 Wacky Message", e.toString());
assertEquals("HELLO", e.getResponseBody());
}
}
@Test
public void testHttp501() throws Exception {
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 501, "Not Implemented"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader("not implemented"), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (NotImplementedOperationException e) {
assertEquals("HTTP 501 Not Implemented", e.getMessage());
}
}
@SuppressWarnings("deprecation")
@Test
public void testInvalidConformanceCall() {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.conformance();
fail();
} catch (IllegalArgumentException e) {
assertEquals("Must call fetchConformance() instead of conformance() for RI/STU3+ structures", e.getMessage());
}
}
@Test
public void testPutDoesntForceAllIdsJson() throws Exception {
IParser p = ourCtx.newJsonParser();
Patient patient = new Patient();
patient.setId("PATIENT1");
patient.addName().addFamily("PATIENT1");
Bundle bundle = new Bundle();
bundle.setId("BUNDLE1");
bundle.addEntry().setResource(patient);
final String encoded = p.encodeResourceToString(bundle);
assertEquals("{\"resourceType\":\"Bundle\",\"id\":\"BUNDLE1\",\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"PATIENT1\",\"name\":[{\"family\":[\"PATIENT1\"]}]}}]}", encoded);
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(encoded), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
client
.update()
.resource(bundle)
.prefer(PreferReturnEnum.REPRESENTATION)
.encodedJson()
.execute();
//@formatter:on
HttpPut httpRequest = (HttpPut) capt.getValue();
assertEquals("http://example.com/fhir/Bundle/BUNDLE1?_format=json", httpRequest.getURI().toASCIIString());
String requestString = IOUtils.toString(httpRequest.getEntity().getContent(), StandardCharsets.UTF_8);
assertEquals(encoded, requestString);
}
@Test
public void testResponseHasContentTypeMissing() throws Exception {
IParser p = ourCtx.newJsonParser();
Patient patient = new Patient();
patient.addName().addFamily("FAM");
final String respString = p.encodeResourceToString(patient);
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(null);
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (NonFhirResponseException e) {
assertEquals("Response contains no Content-Type", e.getMessage());
}
// Patient resp = client.read().resource(Patient.class).withId("1").execute();
// assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString());
}
@Test
public void testResponseHasContentTypeNonFhir() throws Exception {
IParser p = ourCtx.newJsonParser();
Patient patient = new Patient();
patient.addName().addFamily("FAM");
final String respString = p.encodeResourceToString(patient);
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", "text/plain"));
// when(myHttpResponse.getEntity().getContentType()).thenReturn(null);
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.read().resource(Patient.class).withId("1").execute();
fail();
} catch (NonFhirResponseException e) {
assertEquals("Response contains non FHIR Content-Type 'text/plain' : {\"resourceType\":\"Patient\",\"name\":[{\"family\":[\"FAM\"]}]}", e.getMessage());
}
// Patient resp = client.read().resource(Patient.class).withId("1").execute();
// assertEquals("FAM", resp.getNameFirstRep().getFamilyAsSingleString());
}
@Test
public void testSearchByDate() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
DateTimeDt now = DateTimeDt.withCurrentTime();
String dateString = now.getValueAsString().substring(0, 10);
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.BIRTHDATE.after().day(dateString))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?birthdate=gt"+dateString + "&_format=json", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testSearchByString() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value("AAA"))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value(new StringDt("AAA")))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.NAME.matches().values("AAA", "BBB"))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name=AAA,BBB&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
idx++;
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.NAME.matches().values(Arrays.asList("AAA", "BBB")))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name=AAA,BBB&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
idx++;
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.NAME.matchesExactly().value("AAA"))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name%3Aexact=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
idx++;
//@formatter:off
client.search()
.forResource("Patient")
.where(Patient.NAME.matchesExactly().value(new StringDt("AAA")))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name%3Aexact=AAA&_format=json", capt.getAllValues().get(idx).getURI().toString());
idx++;
}
@Test
public void testSearchByUrl() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
client.search()
.forResource("Device")
.where(Device.URL.matches().value("http://foo.com"))
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Device?url=http://foo.com&_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
idx++;
}
@Test
public void testAcceptHeaderWithEncodingSpecified() throws Exception {
final String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).then(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
client.setEncoding(EncodingEnum.JSON);
client.search()
.forResource("Device")
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Device?_format=json", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
assertEquals("application/fhir+json;q=1.0, application/json+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
idx++;
//@formatter:off
client.setEncoding(EncodingEnum.XML);
client.search()
.forResource("Device")
.returnBundle(Bundle.class)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Device?_format=xml", UrlUtil.unescape(capt.getAllValues().get(idx).getURI().toString()));
assertEquals("application/fhir+xml;q=1.0, application/xml+fhir;q=0.9", capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_ACCEPT).getValue());
idx++;
}
@Test
public void testSearchForUnknownType() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
try {
client.search(new UriDt("?aaaa"));
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unable to determine the resource type from the given URI: ?aaaa", e.getMessage());
}
}
@Test
public void testTransactionWithInvalidBody() throws Exception {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
// Transaction
try {
client.transaction().withBundle("FOO");
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
}
// Create
try {
client.create().resource("FOO").execute();
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
}
// Update
try {
client.update().resource("FOO").execute();
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
}
// Validate
try {
client.validate().resource("FOO").execute();
fail();
} catch (IllegalArgumentException e) {
assertEquals("Unable to determing encoding of request (body does not appear to be valid XML or JSON)", e.getMessage());
}
}
//TODO: narratives don't work without stax
@Test
@Ignore
public void testUpdateById() throws Exception {
IParser p = ourCtx.newJsonParser();
OperationOutcome conf = new OperationOutcome();
conf.getText().setDivAsString("OK!");
final String respString = p.encodeResourceToString(conf);
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Patient pt = new Patient();
pt.setId("222");
pt.getText().setDivAsString("A PATIENT");
client.update().resource(pt).withId("111").execute();
ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString());
assertEquals("http://example.com/fhir/Patient/111", capt.getAllValues().get(0).getURI().toASCIIString());
validateUserAgent(capt);
assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", ""));
assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue());
String body = extractBodyAsString(capt);
assertThat(body, containsString("<id value=\"111\"/>"));
}
// TODO: narratives don't work without stax
@Test
@Ignore
public void testUpdateWithPreferRepresentationServerReturnsOO() throws Exception {
final IParser p = ourCtx.newJsonParser();
final OperationOutcome resp0 = new OperationOutcome();
resp0.getText().setDivAsString("OK!");
final Patient resp1 = new Patient();
resp1.getText().setDivAsString("FINAL VALUE");
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
@Override
public Header[] answer(InvocationOnMock theInvocation) throws Throwable {
return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") };
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
if (myAnswerCount++ == 0) {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8"));
} else {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8"));
}
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Patient pt = new Patient();
pt.setId("Patient/222");
pt.getText().setDivAsString("A PATIENT");
MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute();
assertEquals(2, myAnswerCount);
assertNotNull(outcome.getOperationOutcome());
assertNotNull(outcome.getResource());
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">OK!</div>", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString());
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">FINAL VALUE</div>", ((Patient) outcome.getResource()).getText().getDivAsString());
assertEquals(myAnswerCount, capt.getAllValues().size());
assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(0).getURI().toASCIIString());
assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString());
}
@Test
public void testUpdateWithPreferRepresentationServerReturnsResource() throws Exception {
final IParser p = ourCtx.newJsonParser();
final Patient resp1 = new Patient();
resp1.setActive(true);
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
@Override
public Header[] answer(InvocationOnMock theInvocation) throws Throwable {
return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") };
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
myAnswerCount++;
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Patient pt = new Patient();
pt.setId("Patient/222");
pt.getText().setDivAsString("A PATIENT");
MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute();
assertEquals(1, myAnswerCount);
assertNull(outcome.getOperationOutcome());
assertNotNull(outcome.getResource());
assertEquals(true, ((Patient) outcome.getResource()).getActive());
assertEquals(myAnswerCount, capt.getAllValues().size());
assertEquals("http://example.com/fhir/Patient/222?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
}
@Test
public void testUserAgentForConformance() throws Exception {
IParser p = ourCtx.newJsonParser();
Conformance conf = new Conformance();
conf.setCopyright("COPY");
final String respString = p.encodeResourceToString(conf);
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON_NEW + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
client.fetchConformance().ofType(Conformance.class).execute();
assertEquals("http://example.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
validateUserAgent(capt);
}
// TODO: narratives don't work without stax
@Test
@Ignore
public void testValidate() throws Exception {
final IParser p = ourCtx.newXmlParser();
final OperationOutcome resp0 = new OperationOutcome();
resp0.getText().setDivAsString("OK!");
ArgumentCaptor<Request> capt = ArgumentCaptor.forClass(Request.class);
when(myHttpClient.newCall(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.execute().code()).thenReturn(200);
when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer<Header[]>() {
@Override
public Header[] answer(InvocationOnMock theInvocation) throws Throwable {
return new Header[] {};
}
});
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
Patient pt = new Patient();
pt.setId("Patient/222");
pt.getText().setDivAsString("A PATIENT");
MethodOutcome outcome = client.validate().resource(pt).execute();
assertNotNull(outcome.getOperationOutcome());
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">OK!</div>", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString());
}
*/
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass
public static void beforeClass() {
// // Force StAX to fail like it will on android
// System.setProperty("javax.xml.stream.XMLInputFactory", "FOO");
// System.setProperty("javax.xml.stream.XMLOutputFactory", "FOO");
ourCtx = FhirContext.forDstu3();
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>hapi-fhir-base-example-embedded-ws</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
</parent>
<packaging>jar</packaging>

View File

@ -5,6 +5,11 @@
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
@ -13,5 +18,6 @@
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
</natures>
</projectDescription>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -26,6 +26,13 @@
<version>1.6.0</version>
</dependency>
<!-- Use an old version of Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
<!--
Woodstox note: The hapi-fhir-base-testmindeps-client project includes no Woodstox at all, so that we use the
StAX version that's bundled with the JDK. The hapi-fhir-base-testmindeps-server
@ -34,7 +41,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>woodstox-core-asl</artifactId>
@ -49,7 +56,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>woodstox-core-asl</artifactId>
@ -60,7 +67,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>woodstox-core-asl</artifactId>

View File

@ -12,14 +12,14 @@ import ca.uhn.fhir.rest.gclient.ITransactionTyped;
public class ClientTest {
private static final FhirContext ctx = FhirContext.forDstu1();
private static FhirContext ctx = FhirContext.forDstu1();
@Test
public void testTransaction() {
Bundle bundle = new Bundle();
bundle.addEntry().setResource(new Patient().setId("Patient/unit_test_patient"));
IGenericClient client = ctx.newRestfulGenericClient("http://this_is_an_invalid_host_name_yes_it_is/fhir"); // won't connect
IGenericClient client = ctx.newRestfulGenericClient("http://127.0.0.1:1/fhir"); // won't connect
ITransactionTyped<Bundle> transaction = client.transaction().withBundle(bundle);
try {
Bundle result = transaction.encodedJson().execute();

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -25,6 +25,25 @@
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.7</version>
</dependency>
<!-- Xiaomi phones depend on an old version of this... See #394 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.2</version><!--$NO-MVN-MAN-VER$-->
</dependency>
<!-- Use an old version of Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
<!--
Woodstox note: The hapi-fhir-base-testmindeps-client project includes no Woodstox at all, so that we use the
@ -40,7 +59,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>woodstox-core-asl</artifactId>
@ -55,7 +74,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>woodstox-core-asl</artifactId>
@ -66,7 +85,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<artifactId>woodstox-core-asl</artifactId>

View File

@ -1,29 +0,0 @@
package ca.uhn.fhir.context;
import static org.junit.Assert.*;
import org.junit.Test;
public class MultiVersionModelScannerTest {
@Test
public void testScan() {
FhirContext ctx = FhirContext.forDstu1();
RuntimeResourceDefinition def;
def = ctx.getResourceDefinition(ca.uhn.fhir.model.dstu2.resource.Patient.class);
assertEquals(FhirVersionEnum.DSTU2, def.getStructureVersion());
def = ctx.getResourceDefinition(new ca.uhn.fhir.model.dstu2.resource.Patient());
assertEquals(FhirVersionEnum.DSTU2, def.getStructureVersion());
def = ctx.getResourceDefinition("Patient");
assertEquals(FhirVersionEnum.DSTU1, def.getStructureVersion());
assertEquals(ca.uhn.fhir.model.dstu.resource.Patient.class, def.getImplementingClass());
def = ctx.getResourceDefinition(FhirVersionEnum.DSTU2, "Patient");
assertEquals(FhirVersionEnum.DSTU2, def.getStructureVersion());
assertEquals(ca.uhn.fhir.model.dstu2.resource.Patient.class, def.getImplementingClass());
}
}

View File

@ -5,21 +5,29 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.hamcrest.Matchers;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.composite.QuantityDt;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.util.TestUtil;
public class MultiVersionXmlParserTest {
private static final FhirContext ourCtxDstu1 = FhirContext.forDstu1();
private static final FhirContext ourCtxDstu2 = FhirContext.forDstu2();
private static FhirContext ourCtxDstu1 = FhirContext.forDstu1();
private static FhirContext ourCtxDstu2 = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultiVersionXmlParserTest.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testEncodeExtensionFromDifferentVersion() {
Patient p = new Patient();
@ -57,14 +65,14 @@ public class MultiVersionXmlParserTest {
try {
ourCtxDstu1.newXmlParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Patient.class, res);
fail();
} catch (IllegalArgumentException e) {
assertEquals("This parser is for FHIR version DSTU1 - Can not parse a structure for version DSTU2", e.getMessage());
} catch (ConfigurationException e) {
assertEquals("This context is for FHIR version \"DSTU1\" but the class \"ca.uhn.fhir.model.dstu2.resource.Patient\" is for version \"DSTU2\"", e.getMessage());
}
try {
ourCtxDstu2.newXmlParser().parseResource(ca.uhn.fhir.model.dstu.resource.Patient.class, res);
fail();
} catch (IllegalArgumentException e) {
assertEquals("This parser is for FHIR version DSTU2 - Can not parse a structure for version DSTU1", e.getMessage());
} catch (ConfigurationException e) {
assertEquals("This context is for FHIR version \"DSTU2\" but the class \"ca.uhn.fhir.model.dstu.resource.Patient\" is for version \"DSTU1\"", e.getMessage());
}
}

View File

@ -10,27 +10,12 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
public class FhirContextTest {
@Test
public void testWrongVersionDoesntGetInContext1() {
@Test
public void testWrongVersionDoesntGetInContext1() {
FhirContext ctx = FhirContext.forDstu1();
RuntimeResourceDefinition def = ctx.getResourceDefinition("Patient");
assertEquals(Patient.class, def.getImplementingClass());
}
@Test
public void testWrongVersionDoesntGetInContext2() {
FhirContext ctx = FhirContext.forDstu1();
RuntimeResourceDefinition def = ctx.getResourceDefinition("Patient");
assertEquals(Patient.class, def.getImplementingClass());
ctx = FhirContext.forDstu1();
def = ctx.getResourceDefinition(ca.uhn.fhir.model.dstu2.resource.Patient.class);
assertEquals(ca.uhn.fhir.model.dstu2.resource.Patient.class, def.getImplementingClass());
def = ctx.getResourceDefinition("Patient");
assertEquals(Patient.class, def.getImplementingClass());
}
FhirContext ctx = FhirContext.forDstu1();
RuntimeResourceDefinition def = ctx.getResourceDefinition("Patient");
assertEquals(Patient.class, def.getImplementingClass());
}
}

View File

@ -30,7 +30,7 @@
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="owner.project.facets" value="java"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>

View File

@ -6,13 +6,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annota
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
@ -113,6 +108,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
@ -126,8 +122,10 @@ org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
@ -137,6 +135,8 @@ org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
@ -164,16 +164,16 @@ org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=false
org.eclipse.jdt.core.formatter.comment.format_header=true
org.eclipse.jdt.core.formatter.comment.format_html=true
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
org.eclipse.jdt.core.formatter.comment.format_line_comments=false
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=true
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
org.eclipse.jdt.core.formatter.comment.line_length=120
org.eclipse.jdt.core.formatter.comment.line_length=200
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
@ -195,6 +195,7 @@ org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=3
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
@ -379,21 +380,33 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constan
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=false
org.eclipse.jdt.core.formatter.join_wrapped_lines=true
org.eclipse.jdt.core.formatter.join_wrapped_lines=false
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=300
org.eclipse.jdt.core.formatter.lineSplit=250
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=tab
org.eclipse.jdt.core.formatter.tabulation.size=3
org.eclipse.jdt.core.formatter.use_on_off_tags=true
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>1.6-SNAPSHOT</version>
<version>2.1-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -20,12 +20,9 @@
<!-- JSON -->
<dependency>
<groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
</dependency>
<!-- XML -->
@ -138,6 +135,34 @@
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<classFolders>
<classFolder>${basedir}/target/classes</classFolder>
</classFolders>
<sourceFolders>
<sourceFolder>${basedir}/src/main/java</sourceFolder>
</sourceFolders>
<dumpOnExit>true</dumpOnExit>
</configuration>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>${argLine} -Dfile.encoding=UTF-8 -Xmx712m</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>

View File

@ -28,13 +28,11 @@ import java.util.Set;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.model.api.ICodeEnum;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDeclaredChildDefinition {
private Class<? extends ICodeEnum> myCodeType;
private Class<? extends IBase> myDatatype;
private BaseRuntimeElementDefinition<?> myElementDefinition;
@ -87,10 +85,6 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
return null;
}
public Class<? extends ICodeEnum> getCodeType() {
return myCodeType;
}
public Class<? extends IBase> getDatatype() {
return myDatatype;
}
@ -103,14 +97,10 @@ public abstract class BaseRuntimeChildDatatypeDefinition extends BaseRuntimeDecl
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myElementDefinition = theClassToElementDefinitions.get(getDatatype());
assert myElementDefinition != null : "Unknown type: " + getDatatype();
}
public void setCodeType(Class<? extends ICodeEnum> theType) {
if (myElementDefinition != null) {
throw new IllegalStateException("Can not set code type at runtime");
if (myElementDefinition == null) {
myElementDefinition = theContext.getElementDefinition(getDatatype());
}
myCodeType = theType;
assert myElementDefinition != null : "Unknown type: " + getDatatype();
}
@Override

View File

@ -30,23 +30,25 @@ import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.util.ValidateUtil;
public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChildDefinition {
private final IAccessor myAccessor;
private String myBindingValueSet;
private final String myElementName;
private final Field myField;
private final String myFormalDefinition;
private final int myMax;
private final int myMin;
private boolean myModifier;
private final IMutator myMutator;
private final IMutator myMutator;
private final String myShortDefinition;
private boolean mySummary;
BaseRuntimeDeclaredChildDefinition(Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName) throws ConfigurationException {
super();
Validate.notNull(theField, "No field speficied");
Validate.inclusiveBetween(0, Integer.MAX_VALUE, theChildAnnotation.min(), "Min must be >= 0");
ValidateUtil.isGreaterThanOrEqualTo(theChildAnnotation.min(), 0, "Min must be >= 0");
Validate.isTrue(theChildAnnotation.max() == -1 || theChildAnnotation.max() >= theChildAnnotation.min(), "Max must be >= Min (unless it is -1 / unlimited)");
Validate.notBlank(theElementName, "Element name must not be blank");
@ -81,6 +83,10 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
return myAccessor;
}
public String getBindingValueSet() {
return myBindingValueSet;
}
@Override
public String getElementName() {
return myElementName;
@ -129,6 +135,10 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
return mySummary;
}
void setBindingValueSet(String theBindingValueSet) {
myBindingValueSet = theBindingValueSet;
}
protected void setModifier(boolean theModifier) {
myModifier = theModifier;
}

View File

@ -1,5 +1,10 @@
package ca.uhn.fhir.context;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/*
* #%L
* HAPI FHIR - Core Library
@ -23,23 +28,103 @@ package ca.uhn.fhir.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.IBoundCodeableConcept;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.IResourceBlock;
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.model.api.annotation.Binding;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.ReflectionUtil;
public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> extends BaseRuntimeElementDefinition<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeElementCompositeDefinition.class);
private Map<String, Integer> forcedOrder = null;
private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>();
private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions;
private FhirContext myContext;
private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
private List<ScannedField> myScannedFields = new ArrayList<BaseRuntimeElementCompositeDefinition.ScannedField>();
private volatile boolean mySealed;
public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
@SuppressWarnings("unchecked")
public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theName, theImplementingClass, theStandardType);
myContext = theContext;
myClassToElementDefinitions = theClassToElementDefinitions;
/*
* We scan classes for annotated fields in the class but also all of its superclasses
*/
Class<? extends IBase> current = theImplementingClass;
LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
do {
if (forcedOrder == null) {
ChildOrder childOrder = current.getAnnotation(ChildOrder.class);
if (childOrder != null) {
forcedOrder = new HashMap<String, Integer>();
for (int i = 0; i < childOrder.names().length; i++) {
forcedOrder.put(childOrder.names()[i], i);
}
}
}
classes .push(current);
if (IBase.class.isAssignableFrom(current.getSuperclass())) {
current = (Class<? extends IBase>) current.getSuperclass();
} else {
current = null;
}
} while (current != null);
Set<Field> fields = new HashSet<Field>();
for (Class<? extends IBase> nextClass : classes) {
int fieldIndexInClass = 0;
for (Field next : nextClass.getDeclaredFields()) {
if (fields.add(next)) {
ScannedField scannedField = new ScannedField(next, theImplementingClass, fieldIndexInClass == 0);
if (scannedField.getChildAnnotation() != null) {
myScannedFields.add(scannedField);
fieldIndexInClass++;
}
}
}
}
}
void addChild(BaseRuntimeChildDefinition theNext) {
@ -53,11 +138,13 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
public BaseRuntimeChildDefinition getChildByName(String theName){
validateSealed();
BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
return retVal;
}
public BaseRuntimeChildDefinition getChildByNameOrThrowDataFormatException(String theName) throws DataFormatException {
validateSealed();
BaseRuntimeChildDefinition retVal = myNameToChild.get(theName);
if (retVal == null) {
throw new DataFormatException("Unknown child name '" + theName + "' in element " + getName() + " - Valid names are: " + new TreeSet<String>(myNameToChild.keySet()));
@ -66,15 +153,324 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
public List<BaseRuntimeChildDefinition> getChildren() {
validateSealed();
return myChildren;
}
public List<BaseRuntimeChildDefinition> getChildrenAndExtension() {
validateSealed();
return myChildrenAndExtensions;
}
/**
* Has this class been sealed
*/
public boolean isSealed() {
return mySealed;
}
@SuppressWarnings("unchecked")
void populateScanAlso(Set<Class<? extends IBase>> theScanAlso) {
for (ScannedField next : myScannedFields) {
if (IBase.class.isAssignableFrom(next.getElementType())) {
if (next.getElementType().isInterface() == false && Modifier.isAbstract(next.getElementType().getModifiers()) == false) {
theScanAlso.add((Class<? extends IBase>) next.getElementType());
}
}
for (Class<? extends IBase> nextChildType : next.getChoiceTypes()) {
if (nextChildType.isInterface() == false && Modifier.isAbstract(nextChildType.getModifiers()) == false) {
theScanAlso.add(nextChildType);
}
}
}
}
private void scanCompositeElementForChildren() {
Set<String> elementNames = new HashSet<String>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
scanCompositeElementForChildren(elementNames, orderToElementDef, orderToExtensionDef);
if (forcedOrder != null) {
/*
* Find out how many elements don't match any entry in the list
* for forced order. Those elements come first.
*/
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> newOrderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
int unknownCount = 0;
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (!forcedOrder.containsKey(nextEntry.getElementName())) {
newOrderToExtensionDef.put(unknownCount, nextEntry);
unknownCount++;
}
}
for (BaseRuntimeDeclaredChildDefinition nextEntry : orderToElementDef.values()) {
if (forcedOrder.containsKey(nextEntry.getElementName())) {
Integer newOrder = forcedOrder.get(nextEntry.getElementName());
newOrderToExtensionDef.put(newOrder + unknownCount, nextEntry);
}
}
orderToElementDef = newOrderToExtensionDef;
}
// while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() <
// 0) {
// BaseRuntimeDeclaredChildDefinition elementDef =
// orderToElementDef.remove(orderToElementDef.firstKey());
// if (elementDef.getElementName().equals("identifier")) {
// orderToElementDef.put(theIdentifierOrder, elementDef);
// } else {
// throw new ConfigurationException("Don't know how to handle element: "
// + elementDef.getElementName());
// }
// }
TreeSet<Integer> orders = new TreeSet<Integer>();
orders.addAll(orderToElementDef.keySet());
orders.addAll(orderToExtensionDef.keySet());
for (Integer i : orders) {
BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
if (nextChild != null) {
this.addChild(nextChild);
}
BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
if (nextExt != null) {
this.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
}
}
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef,
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
int baseElementOrder = 0;
for (ScannedField next : myScannedFields) {
if (next.isFirstFieldInNewClass()) {
baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
}
Class<?> declaringClass = next.getField().getDeclaringClass();
Description descriptionAnnotation = ModelScanner.pullAnnotation(next.getField(), Description.class);
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
Extension extensionAttr = ModelScanner.pullAnnotation(next.getField(), Extension.class);
if (extensionAttr != null) {
orderMap = theOrderToExtensionDef;
}
Child childAnnotation = next.getChildAnnotation();
Field nextField = next.getField();
String elementName = childAnnotation.name();
int order = childAnnotation.order();
boolean childIsChoiceType = false;
boolean orderIsReplaceParent = false;
if (order == Child.REPLACE_PARENT) {
if (extensionAttr != null) {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
orderIsReplaceParent = true;
order = nextEntry.getKey();
orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
break;
}
}
}
if (order == Child.REPLACE_PARENT) {
throw new ConfigurationException("Field " + nextField.getName() + "' on target type " + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT
+ ") but no parent element with extension URL " + extensionAttr.url() + " could be found on type " + nextField.getDeclaringClass().getSimpleName());
}
} else {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (elementName.equals(nextDef.getElementName())) {
orderIsReplaceParent = true;
order = nextEntry.getKey();
BaseRuntimeDeclaredChildDefinition existing = orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
/*
* See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure
* that the field which replaces is a choice even if it's only a choice of one type - this is because the
* element name when serialized still needs to reflect the datatype
*/
if (existing instanceof RuntimeChildChoiceDefinition) {
childIsChoiceType = true;
}
break;
}
}
if (order == Child.REPLACE_PARENT) {
throw new ConfigurationException("Field " + nextField.getName() + "' on target type " + declaringClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT
+ ") but no parent element with name " + elementName + " could be found on type " + nextField.getDeclaringClass().getSimpleName());
}
}
}
if (order < 0 && order != Child.ORDER_UNKNOWN) {
throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + nextField.getName() + "' on target type: " + declaringClass);
}
if (order != Child.ORDER_UNKNOWN && !orderIsReplaceParent) {
order = order + baseElementOrder;
}
// int min = childAnnotation.min();
// int max = childAnnotation.max();
/*
* Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any given IDs and can be figured out later
*/
if (order == Child.ORDER_UNKNOWN) {
order = Integer.valueOf(0);
while (orderMap.containsKey(order)) {
order++;
}
}
List<Class<? extends IBase>> choiceTypes = next.getChoiceTypes();
if (orderMap.containsKey(order)) {
throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "' - Already had: " + orderMap.get(order).getElementName());
}
if (elementNames.contains(elementName)) {
throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "'");
}
Class<?> nextElementType = next.getElementType();
BaseRuntimeDeclaredChildDefinition def;
if (childAnnotation.name().equals("extension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation, descriptionAnnotation);
} else if (childAnnotation.name().equals("modifierExtension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildExtension(nextField, childAnnotation.name(), childAnnotation, descriptionAnnotation);
} else if (BaseContainedDt.class.isAssignableFrom(nextElementType) || (childAnnotation.name().equals("contained") && IBaseResource.class.isAssignableFrom(nextElementType))) {
/*
* Child is contained resources
*/
def = new RuntimeChildContainedResources(nextField, childAnnotation, descriptionAnnotation, elementName);
} else if (IAnyResource.class.isAssignableFrom(nextElementType) || IResource.class.equals(nextElementType)) {
/*
* Child is a resource as a direct child, as in Bundle.entry.resource
*/
def = new RuntimeChildDirectResource(nextField, childAnnotation, descriptionAnnotation, elementName);
} else {
childIsChoiceType |= choiceTypes.size() > 1;
if (childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) && !IBaseReference.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildChoiceDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, choiceTypes);
} else if (extensionAttr != null) {
/*
* Child is an extension
*/
Class<? extends IBase> et = (Class<? extends IBase>) nextElementType;
Object binder = null;
if (BoundCodeDt.class.isAssignableFrom(nextElementType) || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
binder = ModelScanner.getBoundCodeBinder(nextField);
}
def = new RuntimeChildDeclaredExtensionDefinition(nextField, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et, binder);
if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
((RuntimeChildDeclaredExtensionDefinition)def).setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(nextField));
}
} else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource reference
*/
List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>();
for (Class<? extends IElement> nextType : childAnnotation.type()) {
if (IBaseReference.class.isAssignableFrom(nextType)) {
refTypesList.add(myContext.getVersion().getVersion().isRi() ? IAnyResource.class : IResource.class);
continue;
} else if (IBaseResource.class.isAssignableFrom(nextType) == false) {
throw new ConfigurationException("Field '" + nextField.getName() + "' in class '" + nextField.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName());
}
refTypesList.add((Class<? extends IBaseResource>) nextType);
}
def = new RuntimeChildResourceDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, refTypesList);
} else if (IResourceBlock.class.isAssignableFrom(nextElementType) || IBaseBackboneElement.class.isAssignableFrom(nextElementType)
|| IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7?
*/
Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType;
def = new RuntimeChildResourceBlockDefinition(myContext, nextField, childAnnotation, descriptionAnnotation, elementName, blockDef);
} else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType) || "Type".equals(nextElementType.getSimpleName())
|| IBaseDatatype.class.equals(nextElementType)) {
def = new RuntimeChildAny(nextField, elementName, childAnnotation, descriptionAnnotation);
} else if (IDatatype.class.isAssignableFrom(nextElementType) || IPrimitiveType.class.isAssignableFrom(nextElementType) || ICompositeType.class.isAssignableFrom(nextElementType)
|| IBaseDatatype.class.isAssignableFrom(nextElementType) || IBaseExtension.class.isAssignableFrom(nextElementType)) {
Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType;
if (IPrimitiveType.class.isAssignableFrom(nextElementType)) {
if (nextElementType.equals(BoundCodeDt.class)) {
IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField);
Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(nextField);
def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
Class<? extends Enum<?>> binderType = ModelScanner.determineEnumTypeForBoundField(nextField);
def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binderType);
} else {
def = new RuntimeChildPrimitiveDatatypeDefinition(nextField, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
}
} else {
if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
IValueSetEnumBinder<Enum<?>> binder = ModelScanner.getBoundCodeBinder(nextField);
Class<? extends Enum<?>> enumType = ModelScanner.determineEnumTypeForBoundField(nextField);
def = new RuntimeChildCompositeBoundDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType) || INarrative.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildNarrativeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
} else {
def = new RuntimeChildCompositeDatatypeDefinition(nextField, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
}
}
} else {
throw new ConfigurationException("Field '" + elementName + "' in type '" + declaringClass.getCanonicalName() + "' is not a valid child type: " + nextElementType);
}
Binding bindingAnnotation = ModelScanner.pullAnnotation(nextField, Binding.class);
if (bindingAnnotation != null) {
if (isNotBlank(bindingAnnotation.valueSet())) {
def.setBindingValueSet(bindingAnnotation.valueSet());
}
}
}
orderMap.put(order, def);
elementNames.add(elementName);
}
}
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
if (mySealed) {
return;
}
mySealed = true;
scanCompositeElementForChildren();
super.sealAndInitialize(theContext, theClassToElementDefinitions);
for (BaseRuntimeChildDefinition next : myChildren) {
@ -141,6 +537,16 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
myChildrenAndExtensions=Collections.unmodifiableList(children);
}
@Override
protected void validateSealed() {
if (!mySealed) {
synchronized(myContext) {
sealAndInitialize(myContext, myClassToElementDefinitions);
}
}
}
private static int findIndex(List<BaseRuntimeChildDefinition> theChildren, String theName, boolean theDefaultAtEnd) {
int index = theDefaultAtEnd ? theChildren.size() : -1;
for (ListIterator<BaseRuntimeChildDefinition> iter = theChildren.listIterator(); iter.hasNext(); ) {
@ -152,4 +558,54 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
return index;
}
private static class ScannedField {
private Child myChildAnnotation;
private List<Class<? extends IBase>> myChoiceTypes = new ArrayList<Class<? extends IBase>>();
private Class<?> myElementType;
private Field myField;
private boolean myFirstFieldInNewClass;
public ScannedField(Field theField, Class<?> theClass, boolean theFirstFieldInNewClass) {
myField = theField;
myFirstFieldInNewClass = theFirstFieldInNewClass;
Child childAnnotation = ModelScanner.pullAnnotation(theField, Child.class);
if (childAnnotation == null) {
ourLog.trace("Ignoring non @Child field {} on target type {}", theField.getName(), theClass);
return;
}
if (Modifier.isFinal(theField.getModifiers())) {
ourLog.trace("Ignoring constant {} on target type {}", theField.getName(), theClass);
return;
}
myChildAnnotation = childAnnotation;
myElementType = ModelScanner.determineElementType(theField);
for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
myChoiceTypes.add(nextChoiceType);
}
}
public Child getChildAnnotation() {
return myChildAnnotation;
}
public List<Class<? extends IBase>> getChoiceTypes() {
return myChoiceTypes;
}
public Class<?> getElementType() {
return myElementType;
}
public Field getField() {
return myField;
}
public boolean isFirstFieldInNewClass() {
return myFirstFieldInNewClass;
}
}
}

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.context;
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -35,14 +34,14 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
private static final Class<Void> VOID_CLASS = Void.class;
private final String myName;
private final Class<? extends T> myImplementingClass;
private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
private final Class<? extends T> myImplementingClass;
private final String myName;
private final boolean myStandardType;
private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
public BaseRuntimeElementDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
assert StringUtils.isNotBlank(theName);
@ -60,15 +59,6 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
myImplementingClass = theImplementingClass;
}
public boolean isStandardType() {
return myStandardType;
}
@Override
public String toString() {
return getClass().getSimpleName()+"[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
}
public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) {
if (theExtension == null) {
throw new NullPointerException();
@ -76,56 +66,7 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
myExtensions.add(theExtension);
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
return myExtensions;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
return myExtensionsModifier;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
return myExtensionsNonModifier;
}
/**
* @return Returns null if none
*/
public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl) {
return myUrlToExtension.get(theExtensionUrl);
}
/**
* @return Returns the runtime name for this resource (i.e. the name that
* will be used in encoded messages)
*/
public String getName() {
return myName;
}
public T newInstance() {
return newInstance(null);
}
public T newInstance(Object theArgument) {
try {
if (theArgument == null) {
return getConstructor(null).newInstance(null);
} else {
return getConstructor(theArgument).newInstance(theArgument);
}
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (InvocationTargetException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
} catch (SecurityException e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
}
}
public abstract ChildTypeEnum getChildType();
@SuppressWarnings("unchecked")
private Constructor<T> getConstructor(Object theArgument) {
@ -160,10 +101,61 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
return retVal;
}
/**
* @return Returns null if none
*/
public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl) {
validateSealed();
return myUrlToExtension.get(theExtensionUrl);
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
validateSealed();
return myExtensions;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
validateSealed();
return myExtensionsModifier;
}
public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
validateSealed();
return myExtensionsNonModifier;
}
public Class<? extends T> getImplementingClass() {
return myImplementingClass;
}
/**
* @return Returns the runtime name for this resource (i.e. the name that
* will be used in encoded messages)
*/
public String getName() {
return myName;
}
public boolean isStandardType() {
return myStandardType;
}
public T newInstance() {
return newInstance(null);
}
public T newInstance(Object theArgument) {
try {
if (theArgument == null) {
return getConstructor(null).newInstance(null);
} else {
return getConstructor(theArgument).newInstance(theArgument);
}
} catch (Exception e) {
throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
}
}
/**
* Invoked prior to use to perform any initialization and make object
* mutable.
@ -192,29 +184,41 @@ public abstract class BaseRuntimeElementDefinition<T extends IBase> {
myExtensions = Collections.unmodifiableList(myExtensions);
}
public abstract ChildTypeEnum getChildType();
@Override
public String toString() {
return getClass().getSimpleName()+"[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
}
protected void validateSealed() {
/*
* this does nothing, but BaseRuntimeElementCompositeDefinition
* overrides this method to provide functionality because that class
* defers the dealing process
*/
}
public enum ChildTypeEnum {
COMPOSITE_DATATYPE, PRIMITIVE_DATATYPE, RESOURCE, RESOURCE_REF, RESOURCE_BLOCK,
/**
COMPOSITE_DATATYPE, /**
* HL7.org structure style.
*/
CONTAINED_RESOURCE_LIST, /**
* HAPI structure style.
*/
CONTAINED_RESOURCES, EXTENSION_DECLARED,
ID_DATATYPE,
PRIMITIVE_DATATYPE, /**
* HAPI style.
*/
PRIMITIVE_XHTML,
UNDECL_EXT, EXTENSION_DECLARED,
/**
* HAPI structure style.
*/
CONTAINED_RESOURCES,
ID_DATATYPE,
/**
* HL7.org structure style.
*/
CONTAINED_RESOURCE_LIST,
/**
* HL7.org style.
*/
PRIMITIVE_XHTML_HL7ORG,
RESOURCE,
RESOURCE_BLOCK,
UNDECL_EXT,
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.context;
import java.lang.reflect.Method;
/*
* #%L
* HAPI FHIR - Core Library
@ -22,11 +24,15 @@ package ca.uhn.fhir.context;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
@ -64,7 +70,10 @@ import ca.uhn.fhir.validation.FhirValidator;
* Important usage notes:
* </p>
* <ul>
* <li>Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing threads.</li>
* <li>
* Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing
* threads, except for the {@link #registerCustomType} and {@link #registerCustomTypes} methods.
* </li>
* <li>
* Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode
* to build up an internal model of those classes. For that reason, you should try to create one FhirContext instance
@ -80,45 +89,94 @@ public class FhirContext {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
private volatile Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
private ArrayList<Class<? extends IBase>> myCustomTypes;
private Map<String, Class<? extends IBaseResource>> myDefaultTypeForProfile = new HashMap<String, Class<? extends IBaseResource>>();
private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
private boolean myInitialized;
private HapiLocalizer myLocalizer = new HapiLocalizer();
private volatile Map<String, BaseRuntimeElementDefinition<?>> myNameToElementDefinition = Collections.emptyMap();
private volatile Map<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap();
private volatile Map<String, Class<? extends IBaseResource>> myNameToResourceType;
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
private Set<PerformanceOptionsEnum> myPerformanceOptions = new HashSet<PerformanceOptionsEnum>();
private Collection<Class<? extends IBaseResource>> myResourceTypesToScan;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private final IFhirVersion myVersion;
private Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> myVersionToNameToResourceType = Collections.emptyMap();
private boolean myInitializing;
private ParserOptions myParserOptions = new ParserOptions();
/**
* Default constructor. In most cases this is the right constructor to use.
* Returns the parser options object which will be used to supply default
* options to newly created parsers
*
* @return The parser options - Will not return <code>null</code>
*/
public ParserOptions getParserOptions() {
return myParserOptions;
}
/**
* Sets the parser options object which will be used to supply default
* options to newly created parsers
*
* @param theParserOptions The parser options object - Must not be <code>null</code>
*/
public void setParserOptions(ParserOptions theParserOptions) {
Validate.notNull(theParserOptions, "theParserOptions must not be null");
myParserOptions = theParserOptions;
}
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
*/
@Deprecated
public FhirContext() {
this(EMPTY_LIST);
}
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
*/
@Deprecated
public FhirContext(Class<? extends IBaseResource> theResourceType) {
this(toCollection(theResourceType));
}
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
*/
@Deprecated
public FhirContext(Class<?>... theResourceTypes) {
this(toCollection(theResourceTypes));
}
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}
*/
@Deprecated
public FhirContext(Collection<Class<? extends IBaseResource>> theResourceTypes) {
this(null, theResourceTypes);
}
/**
* In most cases it is recommended that you use one of the static initializer methods instead
* of this method, e.g. {@link #forDstu2()} or {@link #forDstu3()}, but
* this method can also be used if you wish to supply the version programmatically.
*/
public FhirContext(FhirVersionEnum theVersion) {
this(theVersion, null);
}
private FhirContext(FhirVersionEnum theVersion, Collection<Class<? extends IBaseResource>> theResourceTypes) {
VersionUtil.getVersion();
if (theVersion != null) {
if (!theVersion.isPresentOnClasspath()) {
throw new IllegalStateException(getLocalizer().getMessage(FhirContext.class, "noStructuresForSpecifiedVersion", theVersion.name()));
@ -139,14 +197,39 @@ public class FhirContext {
} else {
ourLog.info("Creating new FHIR context for FHIR version [{}]", myVersion.getVersion().name());
}
scanResourceTypes(toElementList(theResourceTypes));
myResourceTypesToScan = theResourceTypes;
/*
* Check if we're running in Android mode and configure the context appropriately if so
*/
try {
Class<?> clazz = Class.forName("ca.uhn.fhir.android.AndroidMarker");
ourLog.info("Android mode detected, configuring FhirContext for Android operation");
try {
Method method = clazz.getMethod("configureContext", FhirContext.class);
method.invoke(null, this);
} catch (Throwable e) {
ourLog.warn("Failed to configure context for Android operation", e);
}
} catch (ClassNotFoundException e) {
ourLog.trace("Android mode not detected");
}
}
private String createUnknownResourceNameError(String theResourceName, FhirVersionEnum theVersion) {
return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion);
}
private void ensureCustomTypeList() {
myClassToElementDefinition.clear();
if (myCustomTypes == null) {
myCustomTypes = new ArrayList<Class<? extends IBase>>();
}
}
/**
* When encoding resources, this setting configures the parser to include
* an entry in the resource's metadata section which indicates which profile(s) the
@ -158,12 +241,18 @@ public class FhirContext {
return myAddProfileTagWhenEncoding;
}
Collection<RuntimeResourceDefinition> getAllResourceDefinitions() {
validateInitialized();
return myNameToResourceDefinition.values();
}
/**
* Returns the default resource type for the given profile
*
* @see #setDefaultTypeForProfile(String, Class)
*/
public Class<? extends IBaseResource> getDefaultTypeForProfile(String theProfile) {
validateInitialized();
return myDefaultTypeForProfile.get(theProfile);
}
@ -173,6 +262,7 @@ public class FhirContext {
*/
@SuppressWarnings("unchecked")
public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IBase> theElementType) {
validateInitialized();
BaseRuntimeElementDefinition<?> retVal = myClassToElementDefinition.get(theElementType);
if (retVal == null) {
retVal = scanDatatype((Class<? extends IElement>) theElementType);
@ -188,11 +278,13 @@ public class FhirContext {
* </p>
*/
public BaseRuntimeElementDefinition<?> getElementDefinition(String theElementName) {
validateInitialized();
return myNameToElementDefinition.get(theElementName.toLowerCase());
}
/** For unit tests only */
int getElementDefinitionCount() {
validateInitialized();
return myClassToElementDefinition.size();
}
@ -200,6 +292,7 @@ public class FhirContext {
* Returns all element definitions (resources, datatypes, etc.)
*/
public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() {
validateInitialized();
return Collections.unmodifiableCollection(myClassToElementDefinition.values());
}
@ -218,12 +311,19 @@ public class FhirContext {
return myNarrativeGenerator;
}
/**
* Get the configured performance options
*/
public Set<PerformanceOptionsEnum> getPerformanceOptions() {
return myPerformanceOptions;
}
/**
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
*/
@SuppressWarnings("unchecked")
public RuntimeResourceDefinition getResourceDefinition(Class<? extends IBaseResource> theResourceType) {
validateInitialized();
if (theResourceType == null) {
throw new NullPointerException("theResourceType can not be null");
}
@ -233,13 +333,14 @@ public class FhirContext {
RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType);
if (retVal == null) {
retVal = scanResourceType((Class<? extends IResource>) theResourceType);
retVal = scanResourceType(theResourceType);
}
return retVal;
}
public RuntimeResourceDefinition getResourceDefinition(FhirVersionEnum theVersion, String theResourceName) {
Validate.notNull(theVersion, "theVersion can not be null");
validateInitialized();
if (theVersion.equals(myVersion.getVersion())) {
return getResourceDefinition(theResourceName);
@ -248,7 +349,8 @@ public class FhirContext {
Map<String, Class<? extends IBaseResource>> nameToType = myVersionToNameToResourceType.get(theVersion);
if (nameToType == null) {
nameToType = new HashMap<String, Class<? extends IBaseResource>>();
ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion);
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> existing = Collections.emptyMap();
ModelScanner.scanVersionPropertyFile(null, nameToType, theVersion, existing);
Map<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>> newVersionToNameToResourceType = new HashMap<FhirVersionEnum, Map<String, Class<? extends IBaseResource>>>();
newVersionToNameToResourceType.putAll(myVersionToNameToResourceType);
@ -269,6 +371,7 @@ public class FhirContext {
* for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinition(IBaseResource theResource) {
validateInitialized();
Validate.notNull(theResource, "theResource must not be null");
return getResourceDefinition(theResource.getClass());
}
@ -280,10 +383,10 @@ public class FhirContext {
* Note that this method is case insensitive!
* </p>
*/
@SuppressWarnings("unchecked")
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
validateInitialized();
Validate.notBlank(theResourceName, "theResourceName must not be blank");
String resourceName = theResourceName.toLowerCase();
RuntimeResourceDefinition retVal = myNameToResourceDefinition.get(resourceName);
@ -293,7 +396,7 @@ public class FhirContext {
throw new DataFormatException(createUnknownResourceNameError(theResourceName, myVersion.getVersion()));
}
if (IBaseResource.class.isAssignableFrom(clazz)) {
retVal = scanResourceType((Class<? extends IResource>) clazz);
retVal = scanResourceType(clazz);
}
}
@ -305,6 +408,7 @@ public class FhirContext {
* for extending the core library.
*/
public RuntimeResourceDefinition getResourceDefinitionById(String theId) {
validateInitialized();
return myIdToResourceDefinition.get(theId);
}
@ -312,13 +416,15 @@ public class FhirContext {
* Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the
* core library.
*/
public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
public Collection<RuntimeResourceDefinition> getResourceDefinitionsWithExplicitId() {
validateInitialized();
return myIdToResourceDefinition.values();
}
/**
* Get the restful client factory. If no factory has been set, this will be initialized with
* Get the restful client factory. If no factory has been set, this will be initialized with
* a new ApacheRestfulClientFactory.
*
* @return the factory used to create the restful clients
*/
public IRestfulClientFactory getRestfulClientFactory() {
@ -329,6 +435,7 @@ public class FhirContext {
}
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
validateInitialized();
return myRuntimeChildUndeclaredExtensionDefinition;
}
@ -344,6 +451,7 @@ public class FhirContext {
* @see #getDefaultTypeForProfile(String)
*/
public boolean hasDefaultTypeForProfile() {
validateInitialized();
return !myDefaultTypeForProfile.isEmpty();
}
@ -385,12 +493,12 @@ public class FhirContext {
* </p>
*
* @param theClientType
* The client type, which is an interface type to be instantiated
* The client type, which is an interface type to be instantiated
* @param theServerBase
* The URL of the base for the restful FHIR server to connect to
* The URL of the base for the restful FHIR server to connect to
* @return A newly created client
* @throws ConfigurationException
* If the interface type is not an interface
* If the interface type is not an interface
*/
public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) {
return getRestfulClientFactory().newClient(theClientType, theServerBase);
@ -407,7 +515,7 @@ public class FhirContext {
* </p>
*
* @param theServerBase
* The URL of the base for the restful FHIR server to connect to
* The URL of the base for the restful FHIR server to connect to
*/
public IGenericClient newRestfulGenericClient(String theServerBase) {
return getRestfulClientFactory().newGenericClient(theServerBase);
@ -420,7 +528,7 @@ public class FhirContext {
/**
* Create a new validator instance.
* <p>
* Note on thread safety: Validators are thread safe, you may use a single validator
* Note on thread safety: Validators are thread safe, you may use a single validator
* in multiple threads. (This is in contrast to parsers)
* </p>
*/
@ -448,6 +556,48 @@ public class FhirContext {
return new XmlParser(this, myParserErrorHandler);
}
/**
* This method may be used to register a custom resource or datatype. Note that by using
* custom types, you are creating a system that will not interoperate with other systems that
* do not know about your custom type. There are valid reasons however for wanting to create
* custom types and this method can be used to enable them.
* <p>
* <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any
* threads are able to call any methods on this context.
* </p>
*
* @param theType
* The custom type to add (must not be <code>null</code>)
*/
public void registerCustomType(Class<? extends IBase> theType) {
Validate.notNull(theType, "theType must not be null");
ensureCustomTypeList();
myCustomTypes.add(theType);
}
/**
* This method may be used to register a custom resource or datatype. Note that by using
* custom types, you are creating a system that will not interoperate with other systems that
* do not know about your custom type. There are valid reasons however for wanting to create
* custom types and this method can be used to enable them.
* <p>
* <b>THREAD SAFETY WARNING:</b> This method is not thread safe. It should be called before any
* threads are able to call any methods on this context.
* </p>
*
* @param theTypes
* The custom types to add (must not be <code>null</code> or contain null elements in the collection)
*/
public void registerCustomTypes(Collection<Class<? extends IBase>> theTypes) {
Validate.notNull(theTypes, "theTypes must not be null");
Validate.noNullElements(theTypes.toArray(), "theTypes must not contain any null elements");
ensureCustomTypeList();
myCustomTypes.addAll(theTypes);
}
private BaseRuntimeElementDefinition<?> scanDatatype(Class<? extends IElement> theResourceType) {
ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
resourceTypes.add(theResourceType);
@ -455,27 +605,46 @@ public class FhirContext {
return defs.get(theResourceType);
}
private RuntimeResourceDefinition scanResourceType(Class<? extends IResource> theResourceType) {
private RuntimeResourceDefinition scanResourceType(Class<? extends IBaseResource> theResourceType) {
ArrayList<Class<? extends IElement>> resourceTypes = new ArrayList<Class<? extends IElement>>();
resourceTypes.add(theResourceType);
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes);
return (RuntimeResourceDefinition) defs.get(theResourceType);
}
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, theResourceTypes);
private synchronized Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IElement>> theResourceTypes) {
myInitializing = true;
List<Class<? extends IBase>> typesToScan = new ArrayList<Class<? extends IBase>>();
if (theResourceTypes != null) {
typesToScan.addAll(theResourceTypes);
}
if (myCustomTypes != null) {
typesToScan.addAll(myCustomTypes);
myCustomTypes = null;
}
ModelScanner scanner = new ModelScanner(this, myVersion.getVersion(), myClassToElementDefinition, typesToScan);
if (myRuntimeChildUndeclaredExtensionDefinition == null) {
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
}
Map<String, BaseRuntimeElementDefinition<?>> nameToElementDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>();
nameToElementDefinition.putAll(myNameToElementDefinition);
nameToElementDefinition.putAll(scanner.getNameToElementDefinitions());
for (Entry<String, BaseRuntimeElementDefinition<?>> next : scanner.getNameToElementDefinitions().entrySet()) {
if (!nameToElementDefinition.containsKey(next.getKey())) {
nameToElementDefinition.put(next.getKey(), next.getValue());
}
}
Map<String, RuntimeResourceDefinition> nameToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
nameToResourceDefinition.putAll(myNameToResourceDefinition);
nameToResourceDefinition.putAll(scanner.getNameToResourceDefinition());
for (Entry<String, RuntimeResourceDefinition> next : scanner.getNameToResourceDefinition().entrySet()) {
if (!nameToResourceDefinition.containsKey(next.getKey())) {
nameToResourceDefinition.put(next.getKey(), next.getValue());
}
}
Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
classToElementDefinition.putAll(myClassToElementDefinition);
classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
@ -500,9 +669,10 @@ public class FhirContext {
myNameToResourceType = scanner.getNameToResourceType();
myInitialized = true;
return classToElementDefinition;
}
/**
* When encoding resources, this setting configures the parser to include
* an entry in the resource's metadata section which indicates which profile(s) the
@ -510,7 +680,7 @@ public class FhirContext {
* <p>
* This feature is intended for situations where custom resource types are being used,
* avoiding the need to manually add profile declarations for these custom types.
* </p>
* </p>
* <p>
* See <a href="http://jamesagnew.gihhub.io/hapi-fhir/doc_extensions.html">Profiling and Extensions</a>
* for more information on using custom types.
@ -530,7 +700,7 @@ public class FhirContext {
/**
* Sets the default type which will be used when parsing a resource that is found to be
* of the given profile.
* of the given profile.
* <p>
* For example, this method is invoked with the profile string of
* <code>"http://example.com/some_patient_profile"</code> and the type of <code>MyPatient.class</code>,
@ -538,13 +708,19 @@ public class FhirContext {
* the <code>MyPatient</code> type will be used unless otherwise specified.
* </p>
*
* @param theProfile The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be <code>null</code> or empty.
* @param theClass The resource type. Must not be <code>null</code> or empty.
* @param theProfile
* The profile string, e.g. <code>"http://example.com/some_patient_profile"</code>. Must not be
* <code>null</code> or empty.
* @param theClass
* The resource type, or <code>null</code> to clear any existing type
*/
public void setDefaultTypeForProfile(String theProfile, Class<? extends IBaseResource> theClass) {
Validate.notBlank(theProfile, "theProfile must not be null or empty");
Validate.notNull(theClass, "theProfile must not be null");
myDefaultTypeForProfile.put(theProfile, theClass);
if (theClass == null) {
myDefaultTypeForProfile.remove(theProfile);
} else {
myDefaultTypeForProfile.put(theProfile, theClass);
}
}
/**
@ -562,15 +738,42 @@ public class FhirContext {
/**
* Sets a parser error handler to use by default on all parsers
*
* @param theParserErrorHandler The error handler
* @param theParserErrorHandler
* The error handler
*/
public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) {
Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
myParserErrorHandler = theParserErrorHandler;
}
/**
* Sets the configured performance options
*
* @see PerformanceOptionsEnum for a list of available options
*/
public void setPerformanceOptions(Collection<PerformanceOptionsEnum> theOptions) {
myPerformanceOptions.clear();
if (theOptions != null) {
myPerformanceOptions.addAll(theOptions);
}
}
/**
* Sets the configured performance options
*
* @see PerformanceOptionsEnum for a list of available options
*/
public void setPerformanceOptions(PerformanceOptionsEnum... thePerformanceOptions) {
Collection<PerformanceOptionsEnum> asList = null;
if (thePerformanceOptions != null) {
asList = Arrays.asList(thePerformanceOptions);
}
setPerformanceOptions(asList);
}
/**
* Set the restful client factory
*
* @param theRestfulClientFactory
*/
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
@ -590,6 +793,12 @@ public class FhirContext {
return resTypes;
}
private void validateInitialized() {
if (!myInitialized && !myInitializing) {
scanResourceTypes(toElementList(myResourceTypesToScan));
}
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU1 DSTU1}
*/
@ -605,7 +814,8 @@ public class FhirContext {
}
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference Implementation Structures)
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2_HL7ORG DSTU2} (using the Reference
* Implementation Structures)
*/
public static FhirContext forDstu2Hl7Org() {
return new FhirContext(FhirVersionEnum.DSTU2_HL7ORG);

View File

@ -34,24 +34,32 @@ public enum FhirVersionEnum {
* ***********************
*/
DSTU1("ca.uhn.fhir.model.dstu.FhirDstu1", null, false),
DSTU1("ca.uhn.fhir.model.dstu.FhirDstu1", null, false, "0.0.82"),
DSTU2("ca.uhn.fhir.model.dstu2.FhirDstu2", null, false),
DSTU2("ca.uhn.fhir.model.dstu2.FhirDstu2", null, false, "1.0.2"),
DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true),
DSTU2_HL7ORG("org.hl7.fhir.instance.FhirDstu2Hl7Org", DSTU2, true, "1.0.2"),
DSTU2_HL7ORG("org.hl7.fhir.instance.FhirDstu2Hl7Org", DSTU2, true);
DSTU2_1("NONE", null, true, "1.4.0"),
DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, "1.6.0");
private final FhirVersionEnum myEquivalent;
private final String myFhirVersionString;
private final boolean myIsRi;
private volatile Boolean myPresentOnClasspath;
private final String myVersionClass;
private volatile IFhirVersion myVersionImplementation;
FhirVersionEnum(String theVersionClass, FhirVersionEnum theEquivalent, boolean theIsRi) {
FhirVersionEnum(String theVersionClass, FhirVersionEnum theEquivalent, boolean theIsRi, String theFhirVersion) {
myVersionClass = theVersionClass;
myEquivalent = theEquivalent;
myIsRi = theIsRi;
myFhirVersionString = theFhirVersion;
}
public String getFhirVersionString() {
return myFhirVersionString;
}
public IFhirVersion getVersionImplementation() {
@ -82,6 +90,10 @@ public enum FhirVersionEnum {
return ordinal() > theVersion.ordinal();
}
public boolean isOlderThan(FhirVersionEnum theVersion) {
return ordinal() < theVersion.ordinal();
}
/**
* Returns true if the given version is present on the classpath
*/

View File

@ -26,7 +26,6 @@ import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
@ -35,34 +34,24 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.CodeableConceptElement;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IBoundCodeableConcept;
import ca.uhn.fhir.model.api.ICodeEnum;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IResource;
@ -72,15 +61,9 @@ import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Compartment;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.ICodedDatatype;
import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
import ca.uhn.fhir.util.ReflectionUtil;
@ -96,13 +79,12 @@ class ModelScanner {
private Map<String, Class<? extends IBaseResource>> myNameToResourceType = new HashMap<String, Class<? extends IBaseResource>>();
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private Set<Class<? extends IBase>> myScanAlso = new HashSet<Class<? extends IBase>>();
private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>();
private FhirVersionEnum myVersion;
private Set<Class<? extends IBase>> myVersionTypes;
ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions,
Collection<Class<? extends IElement>> theResourceTypes) throws ConfigurationException {
Collection<Class<? extends IBase>> theResourceTypes) throws ConfigurationException {
myContext = theContext;
myVersion = theVersion;
Set<Class<? extends IBase>> toScan;
@ -114,14 +96,7 @@ class ModelScanner {
init(theExistingDefinitions, toScan);
}
private void addScanAlso(Class<? extends IBase> theType) {
if (theType.isInterface() || Modifier.isAbstract(theType.getModifiers())) {
return;
}
myScanAlso.add(theType);
}
private Class<?> determineElementType(Field next) {
static Class<?> determineElementType(Field next) {
Class<?> nextElementType = next.getType();
if (List.class.equals(nextElementType)) {
nextElementType = ReflectionUtil.getGenericCollectionTypeOfField(next);
@ -132,23 +107,18 @@ class ModelScanner {
}
@SuppressWarnings("unchecked")
private IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
static IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
if (bound == null) {
throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
}
String fieldName = "VALUESET_BINDER";
try {
Field bindingField = bound.getField("VALUESET_BINDER");
Field bindingField = bound.getField(fieldName);
return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (NoSuchFieldException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (SecurityException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (Exception e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field (must have a field called " + fieldName + ")", e);
}
}
@ -180,7 +150,7 @@ class ModelScanner {
return myRuntimeChildUndeclaredExtensionDefinition;
}
private void init(Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Set<Class<? extends IBase>> theDatatypes) {
private void init(Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingDefinitions, Set<Class<? extends IBase>> theTypesToScan) {
if (theExistingDefinitions != null) {
myClassToElementDefinitions.putAll(theExistingDefinitions);
}
@ -189,17 +159,11 @@ class ModelScanner {
long start = System.currentTimeMillis();
Map<String, Class<? extends IBaseResource>> resourceTypes = myNameToResourceType;
myVersionTypes = scanVersionPropertyFile(theDatatypes, resourceTypes, myVersion);
// toScan.add(DateDt.class);
// toScan.add(CodeDt.class);
// toScan.add(DecimalDt.class);
// toScan.add(AttachmentDt.class);
// toScan.add(ResourceReferenceDt.class);
// toScan.add(QuantityDt.class);
Set<Class<? extends IBase>> typesToScan = theTypesToScan;
myVersionTypes = scanVersionPropertyFile(typesToScan, resourceTypes, myVersion, myClassToElementDefinitions);
do {
for (Class<? extends IBase> nextClass : theDatatypes) {
for (Class<? extends IBase> nextClass : typesToScan) {
scan(nextClass);
}
for (Iterator<Class<? extends IBase>> iter = myScanAlso.iterator(); iter.hasNext();) {
@ -207,17 +171,26 @@ class ModelScanner {
iter.remove();
}
}
theDatatypes.clear();
theDatatypes.addAll(myScanAlso);
typesToScan.clear();
typesToScan.addAll(myScanAlso);
myScanAlso.clear();
} while (!theDatatypes.isEmpty());
} while (!typesToScan.isEmpty());
for (Entry<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> nextEntry : myClassToElementDefinitions.entrySet()) {
if (theExistingDefinitions != null && theExistingDefinitions.containsKey(nextEntry.getKey())) {
continue;
}
BaseRuntimeElementDefinition<?> next = nextEntry.getValue();
next.sealAndInitialize(myContext, myClassToElementDefinitions);
boolean deferredSeal = false;
if (myContext.getPerformanceOptions().contains(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING)) {
if (next instanceof BaseRuntimeElementCompositeDefinition) {
deferredSeal = true;
}
}
if (!deferredSeal) {
next.sealAndInitialize(myContext, myClassToElementDefinitions);
}
}
myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition();
@ -238,7 +211,7 @@ class ModelScanner {
* ones. Annotations can't extend each other or implement interfaces or anything like that, so rather than duplicate all of the annotation processing code this method just creates an interface
* Proxy to simulate the HAPI annotations if the HL7.org ones are found instead.
*/
private <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
static <T extends Annotation> T pullAnnotation(AnnotatedElement theTarget, Class<T> theAnnotationType) {
T retVal = theTarget.getAnnotation(theAnnotationType);
return retVal;
}
@ -258,6 +231,7 @@ class ModelScanner {
@SuppressWarnings("unchecked")
Class<? extends IBaseResource> resClass = (Class<? extends IBaseResource>) theClass;
scanResource(resClass, resourceDefinition);
return;
}
DatatypeDef datatypeDefinition = pullAnnotation(theClass, DatatypeDef.class);
@ -270,11 +244,9 @@ class ModelScanner {
@SuppressWarnings({ "unchecked" })
Class<? extends IPrimitiveType<?>> resClass = (Class<? extends IPrimitiveType<?>>) theClass;
scanPrimitiveDatatype(resClass, datatypeDefinition);
} else {
return;
// throw new ConfigurationException("Resource type contains a @" + DatatypeDef.class.getSimpleName() + " annotation but does not implement " + IDatatype.class.getCanonicalName() + ": " +
// theClass.getCanonicalName());
}
}
return;
}
Block blockDefinition = pullAnnotation(theClass, Block.class);
@ -301,345 +273,35 @@ class ModelScanner {
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
}
RuntimeResourceBlockDefinition resourceDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass));
myClassToElementDefinitions.put(theClass, resourceDef);
scanCompositeElementForChildren(theClass, resourceDef);
RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
myClassToElementDefinitions.put(theClass, blockDef);
}
private void scanCompositeDatatype(Class<? extends ICompositeType> theClass, DatatypeDef theDatatypeDefinition) {
ourLog.debug("Scanning datatype class: {}", theClass.getName());
RuntimeCompositeDatatypeDefinition resourceDef;
RuntimeCompositeDatatypeDefinition elementDef;
if (theClass.equals(ExtensionDt.class)) {
resourceDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass, true);
elementDef = new RuntimeExtensionDtDefinition(theDatatypeDefinition, theClass, true, myContext, myClassToElementDefinitions);
// } else if (IBaseMetaType.class.isAssignableFrom(theClass)) {
// resourceDef = new RuntimeMetaDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
} else {
resourceDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
elementDef = new RuntimeCompositeDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
}
myClassToElementDefinitions.put(theClass, resourceDef);
myNameToElementDefinitions.put(resourceDef.getName().toLowerCase(), resourceDef);
scanCompositeElementForChildren(theClass, resourceDef);
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, BaseRuntimeElementCompositeDefinition<?> theDefinition) {
Set<String> elementNames = new HashSet<String>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToElementDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
LinkedList<Class<? extends IBase>> classes = new LinkedList<Class<? extends IBase>>();
myClassToElementDefinitions.put(theClass, elementDef);
myNameToElementDefinitions.put(elementDef.getName().toLowerCase(), elementDef);
/*
* We scan classes for annotated fields in the class but also all of its superclasses
* See #423:
* If the type contains a field that has a custom type, we want to make
* sure that this type gets scanned as well
*/
Class<? extends IBase> current = theClass;
do {
classes.push(current);
if (IBase.class.isAssignableFrom(current.getSuperclass())) {
current = (Class<? extends IBase>) current.getSuperclass();
} else {
current = null;
}
} while (current != null);
for (Class<? extends IBase> next : classes) {
scanCompositeElementForChildren(next, elementNames, orderToElementDef, orderToExtensionDef);
}
// while (orderToElementDef.size() > 0 && orderToElementDef.firstKey() <
// 0) {
// BaseRuntimeDeclaredChildDefinition elementDef =
// orderToElementDef.remove(orderToElementDef.firstKey());
// if (elementDef.getElementName().equals("identifier")) {
// orderToElementDef.put(theIdentifierOrder, elementDef);
// } else {
// throw new ConfigurationException("Don't know how to handle element: "
// + elementDef.getElementName());
// }
// }
TreeSet<Integer> orders = new TreeSet<Integer>();
orders.addAll(orderToElementDef.keySet());
orders.addAll(orderToExtensionDef.keySet());
for (Integer i : orders) {
BaseRuntimeChildDefinition nextChild = orderToElementDef.get(i);
if (nextChild != null) {
theDefinition.addChild(nextChild);
}
BaseRuntimeDeclaredChildDefinition nextExt = orderToExtensionDef.get(i);
if (nextExt != null) {
theDefinition.addExtension((RuntimeChildDeclaredExtensionDefinition) nextExt);
}
}
elementDef.populateScanAlso(myScanAlso);
}
@SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends IBase> theClass, Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef,
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
int baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
for (Field next : theClass.getDeclaredFields()) {
if (Modifier.isFinal(next.getModifiers())) {
ourLog.trace("Ignoring constant {} on target type {}", next.getName(), theClass);
continue;
}
Child childAnnotation = pullAnnotation(next, Child.class);
if (childAnnotation == null) {
ourLog.trace("Ignoring non @Child field {} on target type {}", next.getName(), theClass);
continue;
}
Description descriptionAnnotation = pullAnnotation(next, Description.class);
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
Extension extensionAttr = pullAnnotation(next, Extension.class);
if (extensionAttr != null) {
orderMap = theOrderToExtensionDef;
}
String elementName = childAnnotation.name();
int order = childAnnotation.order();
boolean childIsChoiceType = false;
if (order == Child.REPLACE_PARENT) {
if (extensionAttr != null) {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (nextDef instanceof RuntimeChildDeclaredExtensionDefinition) {
if (nextDef.getExtensionUrl().equals(extensionAttr.url())) {
order = nextEntry.getKey();
orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
break;
}
}
}
if (order == Child.REPLACE_PARENT) {
throw new ConfigurationException("Field " + next.getName() + "' on target type " + theClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT
+ ") but no parent element with extension URL " + extensionAttr.url() + " could be found on type " + next.getDeclaringClass().getSimpleName());
}
} else {
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
BaseRuntimeDeclaredChildDefinition nextDef = nextEntry.getValue();
if (elementName.equals(nextDef.getElementName())) {
order = nextEntry.getKey();
BaseRuntimeDeclaredChildDefinition existing = orderMap.remove(nextEntry.getKey());
elementNames.remove(elementName);
/*
* See #350 - If the original field (in the superclass) with the given name is a choice, then we need to make sure
* that the field which replaces is a choice even if it's only a choice of one type - this is because the
* element name when serialized still needs to reflect the datatype
*/
if (existing instanceof RuntimeChildChoiceDefinition) {
childIsChoiceType = true;
}
break;
}
}
if (order == Child.REPLACE_PARENT) {
throw new ConfigurationException("Field " + next.getName() + "' on target type " + theClass.getSimpleName() + " has order() of REPLACE_PARENT (" + Child.REPLACE_PARENT
+ ") but no parent element with name " + elementName + " could be found on type " + next.getDeclaringClass().getSimpleName());
}
}
}
if (order < 0 && order != Child.ORDER_UNKNOWN) {
throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + next.getName() + "' on target type: " + theClass);
}
if (order != Child.ORDER_UNKNOWN) {
order = order + baseElementOrder;
}
// int min = childAnnotation.min();
// int max = childAnnotation.max();
/*
* Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict with any given IDs and can be figured out later
*/
if (order == Child.ORDER_UNKNOWN) {
order = Integer.MIN_VALUE;
while (orderMap.containsKey(order)) {
order++;
}
}
List<Class<? extends IBase>> choiceTypes = new ArrayList<Class<? extends IBase>>();
for (Class<? extends IBase> nextChoiceType : childAnnotation.type()) {
choiceTypes.add(nextChoiceType);
}
if (orderMap.containsKey(order)) {
throw new ConfigurationException("Detected duplicate field order '" + childAnnotation.order() + "' for element named '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
}
if (elementNames.contains(elementName)) {
throw new ConfigurationException("Detected duplicate field name '" + elementName + "' in type '" + theClass.getCanonicalName() + "'");
}
Class<?> nextElementType = determineElementType(next);
if (childAnnotation.name().equals("extension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
RuntimeChildExtension def = new RuntimeChildExtension(next, childAnnotation.name(), childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (childAnnotation.name().equals("modifierExtension") && IBaseExtension.class.isAssignableFrom(nextElementType)) {
RuntimeChildExtension def = new RuntimeChildExtension(next, childAnnotation.name(), childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (BaseContainedDt.class.isAssignableFrom(nextElementType) || (childAnnotation.name().equals("contained") && IBaseResource.class.isAssignableFrom(nextElementType))) {
/*
* Child is contained resources
*/
RuntimeChildContainedResources def = new RuntimeChildContainedResources(next, childAnnotation, descriptionAnnotation, elementName);
orderMap.put(order, def);
} else if (IAnyResource.class.isAssignableFrom(nextElementType) || IResource.class.equals(nextElementType)) {
/*
* Child is a resource as a direct child, as in Bundle.entry.resource
*/
RuntimeChildDirectResource def = new RuntimeChildDirectResource(next, childAnnotation, descriptionAnnotation, elementName);
orderMap.put(order, def);
} else {
childIsChoiceType |= choiceTypes.size() > 1;
if (childIsChoiceType && !BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) && !IBaseReference.class.isAssignableFrom(nextElementType)) {
/*
* Child is a choice element
*/
for (Class<? extends IBase> nextType : choiceTypes) {
addScanAlso(nextType);
}
RuntimeChildChoiceDefinition def = new RuntimeChildChoiceDefinition(next, elementName, childAnnotation, descriptionAnnotation, choiceTypes);
orderMap.put(order, def);
} else if (next.getType().equals(ExtensionDt.class)) {
RuntimeChildExtensionDt def = new RuntimeChildExtensionDt(next, elementName, childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
if (IElement.class.isAssignableFrom(nextElementType)) {
addScanAlso((Class<? extends IElement>) nextElementType);
}
} else if (extensionAttr != null) {
/*
* Child is an extension
*/
Class<? extends IBase> et = (Class<? extends IBase>) nextElementType;
Object binder = null;
if (BoundCodeDt.class.isAssignableFrom(nextElementType) || IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
binder = getBoundCodeBinder(next);
}
RuntimeChildDeclaredExtensionDefinition def = new RuntimeChildDeclaredExtensionDefinition(next, childAnnotation, descriptionAnnotation, extensionAttr, elementName, extensionAttr.url(), et,
binder);
if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
def.setEnumerationType(ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next));
}
orderMap.put(order, def);
if (IBase.class.isAssignableFrom(nextElementType)) {
addScanAlso((Class<? extends IBase>) nextElementType);
}
} else if (BaseResourceReferenceDt.class.isAssignableFrom(nextElementType) || IBaseReference.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource reference
*/
List<Class<? extends IBaseResource>> refTypesList = new ArrayList<Class<? extends IBaseResource>>();
for (Class<? extends IElement> nextType : childAnnotation.type()) {
if (IBaseReference.class.isAssignableFrom(nextType)) {
refTypesList.add(myVersion.isRi() ? IAnyResource.class : IResource.class);
continue;
} else if (IBaseResource.class.isAssignableFrom(nextType) == false) {
throw new ConfigurationException("Field '" + next.getName() + "' in class '" + next.getDeclaringClass().getCanonicalName() + "' is of type " + BaseResourceReferenceDt.class + " but contains a non-resource type: " + nextType.getCanonicalName());
}
refTypesList.add((Class<? extends IBaseResource>) nextType);
addScanAlso(nextType);
}
RuntimeChildResourceDefinition def = new RuntimeChildResourceDefinition(next, elementName, childAnnotation, descriptionAnnotation, refTypesList);
orderMap.put(order, def);
} else if (IResourceBlock.class.isAssignableFrom(nextElementType) || IBaseBackboneElement.class.isAssignableFrom(nextElementType)
|| IBaseDatatypeElement.class.isAssignableFrom(nextElementType)) {
/*
* Child is a resource block (i.e. a sub-tag within a resource) TODO: do these have a better name according to HL7?
*/
Class<? extends IBase> blockDef = (Class<? extends IBase>) nextElementType;
addScanAlso(blockDef);
RuntimeChildResourceBlockDefinition def = new RuntimeChildResourceBlockDefinition(next, childAnnotation, descriptionAnnotation, elementName, blockDef);
orderMap.put(order, def);
} else if (IDatatype.class.equals(nextElementType) || IElement.class.equals(nextElementType) || "Type".equals(nextElementType.getSimpleName())
|| IBaseDatatype.class.equals(nextElementType)) {
RuntimeChildAny def = new RuntimeChildAny(next, elementName, childAnnotation, descriptionAnnotation);
orderMap.put(order, def);
} else if (IDatatype.class.isAssignableFrom(nextElementType) || IPrimitiveType.class.isAssignableFrom(nextElementType) || ICompositeType.class.isAssignableFrom(nextElementType)
|| IBaseDatatype.class.isAssignableFrom(nextElementType) || IBaseExtension.class.isAssignableFrom(nextElementType)) {
Class<? extends IBase> nextDatatype = (Class<? extends IBase>) nextElementType;
addScanAlso(nextDatatype);
BaseRuntimeChildDatatypeDefinition def;
if (IPrimitiveType.class.isAssignableFrom(nextElementType)) {
if (nextElementType.equals(BoundCodeDt.class)) {
IValueSetEnumBinder<Enum<?>> binder = getBoundCodeBinder(next);
Class<? extends Enum<?>> enumType = determineEnumTypeForBoundField(next);
def = new RuntimeChildPrimitiveBoundCodeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (IBaseEnumeration.class.isAssignableFrom(nextElementType)) {
Class<? extends Enum<?>> binderType = determineEnumTypeForBoundField(next);
def = new RuntimeChildPrimitiveEnumerationDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binderType);
} else {
def = new RuntimeChildPrimitiveDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
}
} else if (IBaseXhtml.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildXhtmlDatatypeDefinition(next, elementName, descriptionAnnotation, childAnnotation, nextDatatype);
} else {
if (IBoundCodeableConcept.class.isAssignableFrom(nextElementType)) {
IValueSetEnumBinder<Enum<?>> binder = getBoundCodeBinder(next);
Class<? extends Enum<?>> enumType = determineEnumTypeForBoundField(next);
def = new RuntimeChildCompositeBoundDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype, binder, enumType);
} else if (BaseNarrativeDt.class.isAssignableFrom(nextElementType) || INarrative.class.isAssignableFrom(nextElementType)) {
def = new RuntimeChildNarrativeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
} else {
def = new RuntimeChildCompositeDatatypeDefinition(next, elementName, childAnnotation, descriptionAnnotation, nextDatatype);
}
}
CodeableConceptElement concept = pullAnnotation(next, CodeableConceptElement.class);
if (concept != null) {
if (!ICodedDatatype.class.isAssignableFrom(nextDatatype)) {
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is marked as @" + CodeableConceptElement.class.getCanonicalName()
+ " but type is not a subtype of " + ICodedDatatype.class.getName());
} else {
Class<? extends ICodeEnum> type = concept.type();
myScanAlsoCodeTable.add(type);
def.setCodeType(type);
}
}
orderMap.put(order, def);
} else {
throw new ConfigurationException("Field '" + elementName + "' in type '" + theClass.getCanonicalName() + "' is not a valid child type: " + nextElementType);
}
}
elementNames.add(elementName);
}
}
private Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
static Class<? extends Enum<?>> determineEnumTypeForBoundField(Field next) {
@SuppressWarnings("unchecked")
Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) ReflectionUtil.getGenericCollectionTypeOfFieldWithSecondOrderForList(next);
return enumType;
@ -653,28 +315,28 @@ class ModelScanner {
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theClass.getCanonicalName());
}
BaseRuntimeElementDefinition<?> resourceDef;
BaseRuntimeElementDefinition<?> elementDef;
if (theClass.equals(XhtmlDt.class)) {
@SuppressWarnings("unchecked")
Class<XhtmlDt> clazz = (Class<XhtmlDt>) theClass;
resourceDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz, isStandardType(clazz));
elementDef = new RuntimePrimitiveDatatypeNarrativeDefinition(resourceName, clazz, isStandardType(clazz));
} else if (IBaseXhtml.class.isAssignableFrom(theClass)) {
@SuppressWarnings("unchecked")
Class<? extends IBaseXhtml> clazz = (Class<? extends IBaseXhtml>) theClass;
resourceDef = new RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition(resourceName, clazz, isStandardType(clazz));
elementDef = new RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition(resourceName, clazz, isStandardType(clazz));
} else if (IIdType.class.isAssignableFrom(theClass)) {
resourceDef = new RuntimeIdDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
elementDef = new RuntimeIdDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
} else {
resourceDef = new RuntimePrimitiveDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
elementDef = new RuntimePrimitiveDatatypeDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
}
myClassToElementDefinitions.put(theClass, resourceDef);
myClassToElementDefinitions.put(theClass, elementDef);
if (!theDatatypeDefinition.isSpecialization()) {
if (myVersion.isRi() && IDatatype.class.isAssignableFrom(theClass)) {
ourLog.debug("Not adding non RI type {} to RI context", theClass);
} else if (!myVersion.isRi() && !IDatatype.class.isAssignableFrom(theClass)) {
ourLog.debug("Not adding RI type {} to non RI context", theClass);
} else {
myNameToElementDefinitions.put(resourceName, resourceDef);
myNameToElementDefinitions.put(resourceName, elementDef);
}
}
@ -702,7 +364,8 @@ class ModelScanner {
}
}
Class<? extends IBaseResource> builtInType = myNameToResourceType.get(resourceName.toLowerCase());
String resourceNameLowerCase = resourceName.toLowerCase();
Class<? extends IBaseResource> builtInType = myNameToResourceType.get(resourceNameLowerCase);
boolean standardType = builtInType != null && builtInType.equals(theClass) == true;
if (primaryNameProvider) {
if (builtInType != null && builtInType.equals(theClass) == false) {
@ -718,19 +381,25 @@ class ModelScanner {
}
}
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(myContext, resourceName, theClass, resourceDefinition, standardType);
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(myContext, resourceName, theClass, resourceDefinition, standardType, myClassToElementDefinitions);
myClassToElementDefinitions.put(theClass, resourceDef);
if (primaryNameProvider) {
if (resourceDef.getStructureVersion() == myVersion) {
myNameToResourceDefinitions.put(resourceName.toLowerCase(), resourceDef);
myNameToResourceDefinitions.put(resourceNameLowerCase, resourceDef);
}
}
scanCompositeElementForChildren(theClass, resourceDef);
myIdToResourceDefinition.put(resourceId, resourceDef);
scanResourceForSearchParams(theClass, resourceDef);
/*
* See #423:
* If the type contains a field that has a custom type, we want to make
* sure that this type gets scanned as well
*/
resourceDef.populateScanAlso(myScanAlso);
return resourceName;
}
@ -821,10 +490,10 @@ class ModelScanner {
return type;
}
static Set<Class<? extends IBase>> scanVersionPropertyFile(Set<Class<? extends IBase>> theDatatypes, Map<String, Class<? extends IBaseResource>> theResourceTypes, FhirVersionEnum version) {
static Set<Class<? extends IBase>> scanVersionPropertyFile(Set<Class<? extends IBase>> theDatatypes, Map<String, Class<? extends IBaseResource>> theResourceTypes, FhirVersionEnum theVersion, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theExistingElementDefinitions) {
Set<Class<? extends IBase>> retVal = new HashSet<Class<? extends IBase>>();
InputStream str = version.getVersionImplementation().getFhirVersionPropertiesFile();
InputStream str = theVersion.getVersionImplementation().getFhirVersionPropertiesFile();
Properties prop = new Properties();
try {
prop.load(str);
@ -839,6 +508,9 @@ class ModelScanner {
@SuppressWarnings("unchecked")
Class<? extends IBase> dtType = (Class<? extends IBase>) Class.forName(nextValue);
if (theExistingElementDefinitions.containsKey(dtType)) {
continue;
}
retVal.add(dtType);
if (IElement.class.isAssignableFrom(dtType)) {
@ -864,6 +536,9 @@ class ModelScanner {
try {
@SuppressWarnings("unchecked")
Class<? extends IBaseResource> nextClass = (Class<? extends IBaseResource>) Class.forName(nextValue);
if (theExistingElementDefinitions.containsKey(nextClass)) {
continue;
}
if (!IBaseResource.class.isAssignableFrom(nextClass)) {
throw new ConfigurationException("Class is not assignable from " + IBaseResource.class.getSimpleName() + ": " + nextValue);
}
@ -878,6 +553,8 @@ class ModelScanner {
}
} catch (IOException e) {
throw new ConfigurationException("Failed to load model property file from classpath: " + "/ca/uhn/fhir/model/dstu/model.properties");
} finally {
IOUtils.closeQuietly(str);
}
return retVal;

View File

@ -0,0 +1,147 @@
package ca.uhn.fhir.context;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import ca.uhn.fhir.parser.IParser;
/**
* This object supplies default configuration to all {@link IParser parser} instances
* created by a given {@link FhirContext}. It is accessed using {@link FhirContext#getParserOptions()}
* and {@link FhirContext#setParserOptions(ParserOptions)}.
* <p>
* It is fine to share a ParserOptions instances across multiple context instances.
* </p>
*/
public class ParserOptions {
private boolean myStripVersionsFromReferences = true;
private Set<String> myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
/**
* If supplied value(s), any resource references at the specified paths will have their
* resource versions encoded instead of being automatically stripped during the encoding
* process. This setting has no effect on the parsing process.
* <p>
* This method provides a finer-grained level of control than {@link #setStripVersionsFromReferences(boolean)}
* and any paths specified by this method will be encoded even if {@link #setStripVersionsFromReferences(boolean)}
* has been set to <code>true</code> (which is the default)
* </p>
*
* @param thePaths
* A collection of paths for which the resource versions will not be removed automatically
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
* only resource name and field names with dots separating is allowed here (no repetition
* indicators, FluentPath expressions, etc.)
* @see #setStripVersionsFromReferences(boolean)
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
*/
public ParserOptions setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
if (thePaths == null) {
setDontStripVersionsFromReferencesAtPaths((List<String>) null);
} else {
setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths));
}
return this;
}
/**
* If set to <code>true<code> (which is the default), resource references containing a version
* will have the version removed when the resource is encoded. This is generally good behaviour because
* in most situations, references from one resource to another should be to the resource by ID, not
* by ID and version. In some cases though, it may be desirable to preserve the version in resource
* links. In that case, this value should be set to <code>false</code>.
*
* @return Returns the parser instance's configuration setting for stripping versions from resource references when
* encoding. Default is <code>true</code>.
*/
public boolean isStripVersionsFromReferences() {
return myStripVersionsFromReferences ;
}
/**
* If set to <code>true<code> (which is the default), resource references containing a version
* will have the version removed when the resource is encoded. This is generally good behaviour because
* in most situations, references from one resource to another should be to the resource by ID, not
* by ID and version. In some cases though, it may be desirable to preserve the version in resource
* links. In that case, this value should be set to <code>false</code>.
* <p>
* This method provides the ability to globally disable reference encoding. If finer-grained
* control is needed, use {@link #setDontStripVersionsFromReferencesAtPaths(String...)}
* </p>
* @param theStripVersionsFromReferences
* Set this to <code>false<code> to prevent the parser from removing
* resource versions from references.
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
* @see #setDontStripVersionsFromReferencesAtPaths(String...)
*/
public ParserOptions setStripVersionsFromReferences(boolean theStripVersionsFromReferences) {
myStripVersionsFromReferences = theStripVersionsFromReferences;
return this;
}
/**
* If supplied value(s), any resource references at the specified paths will have their
* resource versions encoded instead of being automatically stripped during the encoding
* process. This setting has no effect on the parsing process.
* <p>
* This method provides a finer-grained level of control than {@link #setStripVersionsFromReferences(boolean)}
* and any paths specified by this method will be encoded even if {@link #setStripVersionsFromReferences(boolean)}
* has been set to <code>true</code> (which is the default)
* </p>
*
* @param thePaths
* A collection of paths for which the resource versions will not be removed automatically
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
* only resource name and field names with dots separating is allowed here (no repetition
* indicators, FluentPath expressions, etc.)
* @see #setStripVersionsFromReferences(boolean)
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
*/
@SuppressWarnings("unchecked")
public ParserOptions setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) {
if (thePaths == null) {
myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
} else if (thePaths instanceof HashSet) {
myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>)thePaths).clone();
} else {
myDontStripVersionsFromReferencesAtPaths = new HashSet<String>(thePaths);
}
return this;
}
/**
* Returns the value supplied to {@link IParser#setDontStripVersionsFromReferencesAtPaths(String...)}
*
* @see #setDontStripVersionsFromReferencesAtPaths(String...)
* @see #setStripVersionsFromReferences(boolean)
*/
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
return myDontStripVersionsFromReferencesAtPaths;
}
}

View File

@ -20,21 +20,21 @@ package ca.uhn.fhir.context;
* #L%
*/
import java.lang.reflect.Field;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
/**
* HL7org XHTML type
* This enum contains options to be used for {@link FhirContext#setPerformanceOptions(PerformanceOptionsEnum...)}
*/
public class RuntimeChildXhtmlDatatypeDefinition extends RuntimeChildPrimitiveDatatypeDefinition {
public RuntimeChildXhtmlDatatypeDefinition(Field theField, String theElementName, Description theDescriptionAnnotation, Child theChildAnnotation, Class<? extends IBase> theDatatype) {
super(theField, theElementName, theDescriptionAnnotation, theChildAnnotation, theDatatype);
}
public enum PerformanceOptionsEnum {
/**
* When this option is set, model classes will not be scanned for children until the
* child list for the given type is actually accessed.
* <p>
* The effect of this option is that reflection operations to scan children will be
* deferred, and some may never happen if specific model types aren't actually used.
* This option is useful on environments where reflection is particularly slow, e.g.
* Android or low powered devices.
* </p>
*/
DEFERRED_MODEL_SCANNING
}

View File

@ -21,20 +21,10 @@ package ca.uhn.fhir.context;
*/
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
@ -46,6 +36,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
private Map<Class<? extends IBase>, String> myDatatypeToElementName;
private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition;
private String myReferenceSuffix;
private List<Class<? extends IBaseResource>> myResourceTypes;
public RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation, List<Class<? extends IBase>> theChoiceTypes) {
super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
@ -86,6 +77,7 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>();
myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>();
myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
myResourceTypes = new ArrayList<Class<? extends IBaseResource>>();
if (theContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
myReferenceSuffix = "Resource";
@ -106,6 +98,8 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
myNameToChildDefinition.put(getElementName() + "Reference", nextDef);
myNameToChildDefinition.put(getElementName() + "Resource", nextDef);
myResourceTypes.add((Class<? extends IBaseResource>) next);
} else {
nextDef = theClassToElementDefinitions.get(next);
@ -166,10 +160,14 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition);
myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName);
myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition);
myResourceTypes = Collections.unmodifiableList(myResourceTypes);
}
public List<Class<? extends IBaseResource>> getResourceTypes() {
return myResourceTypes;
}
@Override
public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
String retVal = myDatatypeToElementName.get(theDatatype);

View File

@ -158,6 +158,15 @@ public class RuntimeChildDeclaredExtensionDefinition extends BaseRuntimeDeclared
myUrlToChildExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(myChildType);
/*
* This will happen for any type that isn't defined in the base set of
* built-in types, e.g. custom structures or custom extensions
*/
if (elementDef == null) {
elementDef = theContext.getElementDefinition(myChildType);
}
if (elementDef instanceof RuntimePrimitiveDatatypeDefinition || elementDef instanceof RuntimeCompositeDatatypeDefinition) {
myDatatypeChildName = "value" + elementDef.getName().substring(0, 1).toUpperCase() + elementDef.getName().substring(1);
if ("valueResourceReference".equals(myDatatypeChildName)) {

View File

@ -46,6 +46,14 @@ public class RuntimeChildExtension extends RuntimeChildAny {
public Set<String> getValidChildNames() {
return Collections.singleton(getElementName());
}
@Override
public BaseRuntimeElementDefinition<?> getChildByName(String theName) {
if ("extension".equals(theName) || "modifierExtension".equals(theName)) {
return super.getChildByName("extensionExtension");
}
return super.getChildByName(theName);
}
// @Override
// public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) {

View File

@ -34,21 +34,27 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil
private RuntimeResourceBlockDefinition myElementDef;
private Class<? extends IBase> myResourceBlockType;
private FhirContext myContext;
public RuntimeChildResourceBlockDefinition(Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName, Class<? extends IBase> theResourceBlockType) throws ConfigurationException {
public RuntimeChildResourceBlockDefinition(FhirContext theContext, Field theField, Child theChildAnnotation, Description theDescriptionAnnotation, String theElementName, Class<? extends IBase> theResourceBlockType) throws ConfigurationException {
super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
myContext = theContext;
myResourceBlockType = theResourceBlockType;
}
@Override
public RuntimeResourceBlockDefinition getChildByName(String theName) {
if (getElementName().equals(theName)) {
return myElementDef;
return getDefinition();
}else {
return null;
}
}
private RuntimeResourceBlockDefinition getDefinition() {
return (RuntimeResourceBlockDefinition) myContext.getElementDefinition(myResourceBlockType);
}
@Override
public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
if (myResourceBlockType.equals(theDatatype)) {
@ -60,7 +66,7 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil
@Override
public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) {
if (myResourceBlockType.equals(theDatatype)) {
return myElementDef;
return getDefinition();
}
return null;
}
@ -72,7 +78,7 @@ public class RuntimeChildResourceBlockDefinition extends BaseRuntimeDeclaredChil
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
myElementDef = (RuntimeResourceBlockDefinition) theClassToElementDefinitions.get(myResourceBlockType);
// myElementDef = (RuntimeResourceBlockDefinition) theClassToElementDefinitions.get(myResourceBlockType);
}
}

View File

@ -36,8 +36,8 @@ public class RuntimeCompositeDatatypeDefinition extends BaseRuntimeElementCompos
private Class<? extends IBaseDatatype> myProfileOfType;
private BaseRuntimeElementDefinition<?> myProfileOf;
public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType) {
super(theDef.name(), theImplementingClass, theStandardType);
public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theDef.name(), theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
String resourceName = theDef.name();
if (isBlank(resourceName)) {
@ -81,6 +81,7 @@ public class RuntimeCompositeDatatypeDefinition extends BaseRuntimeElementCompos
@Override
public boolean isProfileOf(Class<? extends IBaseDatatype> theType) {
validateSealed();
if (myProfileOfType != null) {
if (myProfileOfType.equals(theType)) {
return true;

View File

@ -34,8 +34,8 @@ public class RuntimeExtensionDtDefinition extends RuntimeCompositeDatatypeDefini
private List<BaseRuntimeChildDefinition> myChildren;
public RuntimeExtensionDtDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType) {
super(theDef, theImplementingClass, theStandardType);
public RuntimeExtensionDtDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theDef, theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
}
@Override
@ -43,6 +43,10 @@ public class RuntimeExtensionDtDefinition extends RuntimeCompositeDatatypeDefini
return myChildren;
}
public List<BaseRuntimeChildDefinition> getChildrenIncludingUrl() {
return super.getChildren();
}
@Override
public void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super.sealAndInitialize(theContext, theClassToElementDefinitions);

View File

@ -21,34 +21,66 @@ package ca.uhn.fhir.context;
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.util.CoverageIgnore;
public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefinition<IPrimitiveType<?>> implements IRuntimeDatatypeDefinition {
private Class<?> myNativeType;
private BaseRuntimeElementDefinition<?> myProfileOf;
private Class<? extends IBaseDatatype> myProfileOfType;
private boolean mySpecialization;
public RuntimePrimitiveDatatypeDefinition(DatatypeDef theDef, Class<? extends IPrimitiveType<?>> theImplementingClass, boolean theStandardType) {
super(theDef.name(), theImplementingClass, theStandardType);
String resourceName = theDef.name();
if (isBlank(resourceName)) {
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource name: " + theImplementingClass.getCanonicalName());
}
mySpecialization = theDef.isSpecialization();
myProfileOfType = theDef.profileOf();
if (myProfileOfType.equals(IBaseDatatype.class)) {
myProfileOfType = null;
}
determineNativeType(theImplementingClass);
}
private void determineNativeType(Class<? extends IPrimitiveType<?>> theImplementingClass) {
Class<?> clazz = theImplementingClass;
while (clazz.equals(Object.class) == false) {
Type type = clazz.getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType superPt = (ParameterizedType) type;
Type rawType = superPt.getRawType();
if (rawType instanceof Class) {
Class<?> rawClass = (Class<?>) rawType;
if (rawClass.getName().endsWith(".BasePrimitive") || rawClass.getName().endsWith(".PrimitiveType")) {
Type typeVariable = superPt.getActualTypeArguments()[0];
if (typeVariable instanceof Class) {
myNativeType = (Class<?>) typeVariable;
break;
}
}
}
}
clazz = clazz.getSuperclass();
}
}
@Override
@ -56,11 +88,27 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
return ChildTypeEnum.PRIMITIVE_DATATYPE;
}
public Class<?> getNativeType() {
return myNativeType;
}
@Override
public Class<? extends IBaseDatatype> getProfileOf() {
return myProfileOfType;
}
@Override
public boolean isProfileOf(Class<? extends IBaseDatatype> theType) {
if (myProfileOfType != null) {
if (myProfileOfType.equals(theType)) {
return true;
} else if (myProfileOf instanceof IRuntimeDatatypeDefinition) {
return ((IRuntimeDatatypeDefinition) myProfileOf).isProfileOf(theType);
}
}
return false;
}
@Override
public boolean isSpecialization() {
return mySpecialization;
@ -69,7 +117,7 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super.sealAndInitialize(theContext, theClassToElementDefinitions);
if (myProfileOfType != null) {
myProfileOf = theClassToElementDefinitions.get(myProfileOfType);
if (myProfileOf == null) {
@ -85,17 +133,4 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
}
}
@Override
public boolean isProfileOf(Class<? extends IBaseDatatype> theType) {
if (myProfileOfType != null) {
if (myProfileOfType.equals(theType)) {
return true;
} else if (myProfileOf instanceof IRuntimeDatatypeDefinition) {
return ((IRuntimeDatatypeDefinition) myProfileOf).isProfileOf(theType);
}
}
return false;
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.context;
import java.util.Map;
/*
* #%L
* HAPI FHIR - Core Library
@ -24,8 +26,8 @@ import org.hl7.fhir.instance.model.api.IBase;
public class RuntimeResourceBlockDefinition extends BaseRuntimeElementCompositeDefinition<IBase> {
public RuntimeResourceBlockDefinition(String theName, Class<? extends IBase> theImplementingClass, boolean theStandardType) {
super(theName, theImplementingClass, theStandardType);
public RuntimeResourceBlockDefinition(String theName, Class<? extends IBase> theImplementingClass, boolean theStandardType, FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theName, theImplementingClass, theStandardType, theContext, theClassToElementDefinitions);
}
@Override

View File

@ -36,7 +36,7 @@ import ca.uhn.fhir.util.UrlUtil;
public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseResource> {
private RuntimeResourceDefinition myBaseDefinition;
private Class<? extends IBaseResource> myBaseType;
private Map<String, List<RuntimeSearchParam>> myCompartmentNameToSearchParams;
private FhirContext myContext;
private String myId;
@ -45,20 +45,24 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
private String myResourceProfile;
private List<RuntimeSearchParam> mySearchParams;
private final FhirVersionEnum myStructureVersion;
private volatile RuntimeResourceDefinition myBaseDefinition;
public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType) {
super(theResourceName, theClass, theStandardType);
public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super(theResourceName, theClass, theStandardType, theContext, theClassToElementDefinitions);
myContext = theContext;
myResourceProfile = theResourceAnnotation.profile();
myId = theResourceAnnotation.id();
IBaseResource instance;
try {
IBaseResource instance = theClass.newInstance();
myStructureVersion = instance.getStructureFhirVersionEnum();
assert myStructureVersion != null;
instance = theClass.newInstance();
} catch (Exception e) {
throw new ConfigurationException(myContext.getLocalizer().getMessage(getClass(), "nonInstantiableType", theClass.getName(), e.toString()), e);
}
myStructureVersion = instance.getStructureFhirVersionEnum();
if (myStructureVersion != theContext.getVersion().getVersion()) {
throw new ConfigurationException(myContext.getLocalizer().getMessage(getClass(), "typeWrongVersion", theContext.getVersion().getVersion(), theClass.getName(), myStructureVersion));
}
}
@ -76,6 +80,10 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
* </p>
*/
public RuntimeResourceDefinition getBaseDefinition() {
validateSealed();
if (myBaseDefinition == null) {
myBaseDefinition = myContext.getResourceDefinition(myBaseType);
}
return myBaseDefinition;
}
@ -105,6 +113,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
}
public String getResourceProfile(String theServerBase) {
validateSealed();
String profile;
if (!myResourceProfile.isEmpty()) {
profile = myResourceProfile;
@ -128,10 +137,12 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
}
public RuntimeSearchParam getSearchParam(String theName) {
validateSealed();
return myNameToSearchParam.get(theName);
}
public List<RuntimeSearchParam> getSearchParams() {
validateSealed();
return mySearchParams;
}
@ -139,6 +150,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
* Will not return null
*/
public List<RuntimeSearchParam> getSearchParamsForCompartmentName(String theCompartmentName) {
validateSealed();
List<RuntimeSearchParam> retVal = myCompartmentNameToSearchParams.get(theCompartmentName);
if (retVal == null) {
return Collections.emptyList();
@ -154,6 +166,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
return "Bundle".equals(getName());
}
@SuppressWarnings("unchecked")
@Override
public void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super.sealAndInitialize(theContext, theClassToElementDefinitions);
@ -183,17 +196,18 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
myCompartmentNameToSearchParams = Collections.unmodifiableMap(compartmentNameToSearchParams);
Class<?> target = getImplementingClass();
myBaseDefinition = this;
myBaseType = (Class<? extends IBaseResource>) target;
do {
target = target.getSuperclass();
if (IBaseResource.class.isAssignableFrom(target) && target.getAnnotation(ResourceDef.class) != null) {
myBaseDefinition = (RuntimeResourceDefinition) theClassToElementDefinitions.get(target);
myBaseType = (Class<? extends IBaseResource>) target;
}
} while (target.equals(Object.class) == false);
}
@Deprecated
public synchronized IBaseResource toProfile() {
validateSealed();
if (myProfileDef != null) {
return myProfileDef;
}
@ -205,6 +219,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
}
public synchronized IBaseResource toProfile(String theServerBase) {
validateSealed();
if (myProfileDef != null) {
return myProfileDef;
}

View File

@ -1,45 +0,0 @@
package ca.uhn.fhir.context;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseReference;
public class RuntimeResourceReferenceDefinition extends BaseRuntimeElementCompositeDefinition<IBaseReference> {
public RuntimeResourceReferenceDefinition(String theName, Class<? extends IBaseReference> theImplementingClass, boolean theStandardType) {
super(theName, theImplementingClass, theStandardType);
}
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super.sealAndInitialize(theContext, theClassToElementDefinitions);
}
@Override
public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() {
return ChildTypeEnum.RESOURCE_REF;
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.i18n;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR - Core Library
@ -10,7 +12,7 @@ package ca.uhn.fhir.i18n;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -21,6 +23,8 @@ package ca.uhn.fhir.i18n;
*/
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
@ -30,40 +34,65 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class HapiLocalizer {
private ResourceBundle myBundle;
public static final String UNKNOWN_I18N_KEY_MESSAGE = "!MESSAGE!";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiLocalizer.class);
private List<ResourceBundle> myBundle = new ArrayList<ResourceBundle>();
private final Map<String, MessageFormat> myKeyToMessageFormat = new ConcurrentHashMap<String, MessageFormat>();
private String[] myBundleNames;
public HapiLocalizer() {
myBundle = ResourceBundle.getBundle(HapiLocalizer.class.getPackage().getName() + ".hapi-messages");
this(HapiLocalizer.class.getPackage().getName() + ".hapi-messages");
}
public HapiLocalizer(String... theBundleNames) {
myBundleNames = theBundleNames;
init();
}
protected void init() {
for (String nextName : myBundleNames) {
myBundle.add(ResourceBundle.getBundle(nextName));
}
}
private String findFormatString(String theQualifiedKey) {
String formatString = null;
for (ResourceBundle nextBundle : myBundle) {
if (nextBundle.containsKey(theQualifiedKey)) {
formatString = nextBundle.getString(theQualifiedKey);
}
if (isNotBlank(formatString)) {
break;
}
}
if (formatString == null) {
ourLog.warn("Unknown localization key: {}", theQualifiedKey);
formatString = UNKNOWN_I18N_KEY_MESSAGE;
}
return formatString;
}
public String getMessage(Class<?> theType, String theKey, Object... theParameters) {
return getMessage(theType.getName() + '.' + theKey, theParameters);
}
public String getMessage(String theQualifiedKey, Object... theParameters) {
if (theParameters != null && theParameters.length > 0) {
MessageFormat format = myKeyToMessageFormat.get(theQualifiedKey);
if (format != null) {
return format.format(theParameters).toString();
}
String formatString = myBundle.getString(theQualifiedKey);
if (formatString== null) {
formatString = "!MESSAGE!";
}
String formatString = findFormatString(theQualifiedKey);
format = new MessageFormat(formatString.trim());
myKeyToMessageFormat.put(theQualifiedKey, format);
return format.format(theParameters).toString();
} else {
String retVal = myBundle.getString(theQualifiedKey);
if (retVal == null) {
retVal = "!MESSAGE!";
}
String retVal = findFormatString(theQualifiedKey);
return retVal;
}
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.model.api;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.CoverageIgnore;
public abstract class BaseIdentifiableElement extends BaseElement implements IIdentifiableElement {
@ -36,6 +37,7 @@ public abstract class BaseIdentifiableElement extends BaseElement implements IId
* @deprecated Use {@link #getElementSpecificId()} instead. This method will be removed because it is easily
* confused with other ID methods (such as patient#getIdentifier)
*/
@CoverageIgnore
@Deprecated
@Override
public IdDt getId() {
@ -55,6 +57,7 @@ public abstract class BaseIdentifiableElement extends BaseElement implements IId
* @deprecated Use {@link #setElementSpecificId(String)} instead. This method will be removed because it is easily
* confused with other ID methods (such as patient#getIdentifier)
*/
@CoverageIgnore
@Deprecated
@Override
public void setId(IdDt theId) {
@ -69,27 +72,33 @@ public abstract class BaseIdentifiableElement extends BaseElement implements IId
* @deprecated Use {@link #setElementSpecificId(String)} instead. This method will be removed because it is easily
* confused with other ID methods (such as patient#getIdentifier)
*/
@CoverageIgnore
@Override
@Deprecated
public void setId(String theId) {
myElementSpecificId = theId;
}
@CoverageIgnore
private static class LockedId extends IdDt {
@CoverageIgnore
public LockedId() {
}
@CoverageIgnore
public LockedId(String theElementSpecificId) {
super(theElementSpecificId);
}
@Override
@CoverageIgnore
public IdDt setValue(String theValue) throws DataFormatException {
throw new UnsupportedOperationException("Use IElement#setElementSpecificId(String) to set the element ID for an element");
}
@Override
@CoverageIgnore
public void setValueAsString(String theValue) throws DataFormatException {
throw new UnsupportedOperationException("Use IElement#setElementSpecificId(String) to set the element ID for an element");
}

View File

@ -1,449 +0,0 @@
package ca.uhn.fhir.model.api;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.base.resource.ResourceMetadataMap;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.UrlUtil;
public class Bundle extends BaseBundle implements IBase /* implements IElement */{
private ResourceMetadataMap myResourceMetadata;
private BoundCodeDt<BundleTypeEnum> myType;
private StringDt myBundleId;
private TagList myCategories;
private List<BundleEntry> myEntries;
private volatile transient Map<IdDt, IResource> myIdToEntries;
private StringDt myLinkBase;
private StringDt myLinkFirst;
private StringDt myLinkLast;
private StringDt myLinkNext;
private StringDt myLinkPrevious;
private StringDt myLinkSelf;
private StringDt myTitle;
private IntegerDt myTotalResults;
/**
* @deprecated Tags wil become immutable in a future release of HAPI, so
* {@link #addCategory(String, String, String)} should be used instead
*/
@Deprecated
public Tag addCategory() {
Tag retVal = new Tag();
getCategories().add(retVal);
return retVal;
}
public void addCategory(String theScheme, String theTerm, String theLabel) {
getCategories().add(new Tag(theScheme, theTerm, theLabel));
}
public void addCategory(Tag theTag) {
getCategories().add(theTag);
}
/**
* Adds and returns a new bundle entry
*/
public BundleEntry addEntry() {
BundleEntry retVal = new BundleEntry();
getEntries().add(retVal);
return retVal;
}
/**
* Adds a new entry
*
* @param theBundleEntry
* The entry to add
*/
public void addEntry(BundleEntry theBundleEntry) {
Validate.notNull(theBundleEntry, "theBundleEntry can not be null");
getEntries().add(theBundleEntry);
}
/**
* Creates a new entry using the given resource and populates it accordingly
*
* @param theResource
* The resource to add
* @return Returns the newly created bundle entry that was added to the bundle
*/
public BundleEntry addResource(IResource theResource, FhirContext theContext, String theServerBase) {
BundleEntry entry = addEntry();
entry.setResource(theResource);
RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
String title = ResourceMetadataKeyEnum.TITLE.get(theResource);
if (title != null) {
entry.getTitle().setValue(title);
} else {
entry.getTitle().setValue(def.getName() + " " + StringUtils.defaultString(theResource.getId().getValue(), "(no ID)"));
}
if (theResource.getId() != null) {
if (theResource.getId().isAbsolute()) {
entry.getLinkSelf().setValue(theResource.getId().getValue());
entry.getId().setValue(theResource.getId().toVersionless().getValue());
} else if (StringUtils.isNotBlank(theResource.getId().getValue())) {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
if (b.length() > 0 && b.charAt(b.length() - 1) != '/') {
b.append('/');
}
b.append(def.getName());
b.append('/');
String resId = theResource.getId().getIdPart();
b.append(resId);
entry.getId().setValue(b.toString());
if (isNotBlank(theResource.getId().getVersionIdPart())) {
b.append('/');
b.append(Constants.PARAM_HISTORY);
b.append('/');
b.append(theResource.getId().getVersionIdPart());
} else {
IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(theResource);
if (versionId != null) {
b.append('/');
b.append(Constants.PARAM_HISTORY);
b.append('/');
b.append(versionId.getValue());
}
}
String qualifiedId = b.toString();
entry.getLinkSelf().setValue(qualifiedId);
// String resourceType = theContext.getResourceDefinition(theResource).getName();
}
}
InstantDt published = ResourceMetadataKeyEnum.PUBLISHED.get(theResource);
if (published == null) {
entry.getPublished().setToCurrentTimeInLocalTimeZone();
} else {
entry.setPublished(published);
}
InstantDt updated = ResourceMetadataKeyEnum.UPDATED.get(theResource);
if (updated != null) {
entry.setUpdated(updated);
}
InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(theResource);
if (deleted != null) {
entry.setDeleted(deleted);
}
IdDt previous = ResourceMetadataKeyEnum.PREVIOUS_ID.get(theResource);
if (previous != null) {
entry.getLinkAlternate().setValue(previous.withServerBase(theServerBase, def.getName()).getValue());
}
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tagList != null) {
for (Tag nextTag : tagList) {
entry.addCategory(nextTag);
}
}
String linkSearch = ResourceMetadataKeyEnum.LINK_SEARCH.get(theResource);
if (isNotBlank(linkSearch)) {
if (!UrlUtil.isAbsolute(linkSearch)) {
linkSearch = (theServerBase + "/" + linkSearch);
}
entry.getLinkSearch().setValue(linkSearch);
}
String linkAlternate = ResourceMetadataKeyEnum.LINK_ALTERNATE.get(theResource);
if (isNotBlank(linkAlternate)) {
if (!UrlUtil.isAbsolute(linkAlternate)) {
linkSearch = (theServerBase + "/" + linkAlternate);
}
entry.getLinkAlternate().setValue(linkSearch);
}
BundleEntrySearchModeEnum entryStatus = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(theResource);
if (entryStatus != null) {
entry.getSearchMode().setValueAsEnum(entryStatus);
}
BundleEntryTransactionMethodEnum entryTransactionOperation = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(theResource);
if (entryTransactionOperation != null) {
entry.getTransactionMethod().setValueAsEnum(entryTransactionOperation);
}
DecimalDt entryScore = ResourceMetadataKeyEnum.ENTRY_SCORE.get(theResource);
if (entryScore != null) {
entry.setScore(entryScore);
}
return entry;
}
public StringDt getBundleId() {
if (myBundleId == null) {
myBundleId = new StringDt();
}
return myBundleId;
}
public TagList getCategories() {
if (myCategories == null) {
myCategories = new TagList();
}
return myCategories;
}
public List<BundleEntry> getEntries() {
if (myEntries == null) {
myEntries = new ArrayList<BundleEntry>();
}
return myEntries;
}
public StringDt getLinkBase() {
if (myLinkBase == null) {
myLinkBase = new StringDt();
}
return myLinkBase;
}
public StringDt getLinkFirst() {
if (myLinkFirst == null) {
myLinkFirst = new StringDt();
}
return myLinkFirst;
}
public StringDt getLinkLast() {
if (myLinkLast == null) {
myLinkLast = new StringDt();
}
return myLinkLast;
}
public StringDt getLinkNext() {
if (myLinkNext == null) {
myLinkNext = new StringDt();
}
return myLinkNext;
}
public StringDt getLinkPrevious() {
if (myLinkPrevious == null) {
myLinkPrevious = new StringDt();
}
return myLinkPrevious;
}
public StringDt getLinkSelf() {
if (myLinkSelf == null) {
myLinkSelf = new StringDt();
}
return myLinkSelf;
}
/*
public InstantDt getPublished() {
InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED);
if (retVal == null) {
retVal= new InstantDt();
getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, retVal);
}
return retVal;
}
*/
/**
* Retrieves a resource from a bundle given its logical ID.
* <p>
* <b>Important usage notes</b>: This method ignores base URLs (so passing in an ID of
* <code>http://foo/Patient/123</code> will return a resource if it has the logical ID of
* <code>http://bar/Patient/123</code>. Also, this method is intended to be used for bundles which have already been
* populated. It will cache its results for fast performance, but that means that modifications to the bundle after
* this method is called may not be accurately reflected.
* </p>
*
* @param theId
* The resource ID
* @return Returns the resource with the given ID, or <code>null</code> if none is found
*/
public IResource getResourceById(IdDt theId) {
Map<IdDt, IResource> map = myIdToEntries;
if (map == null) {
map = new HashMap<IdDt, IResource>();
for (BundleEntry next : this.getEntries()) {
if (next.getId().isEmpty() == false) {
map.put(next.getId().toUnqualified(), next.getResource());
}
}
myIdToEntries = map;
}
return map.get(theId.toUnqualified());
}
public ResourceMetadataMap getResourceMetadata() {
if (myResourceMetadata == null) {
myResourceMetadata = new ResourceMetadataMap();
}
return myResourceMetadata;
}
/**
* Returns a list containing all resources of the given type from this bundle
*/
public <T extends IResource> List<T> getResources(Class<T> theClass) {
ArrayList<T> retVal = new ArrayList<T>();
for (BundleEntry next : getEntries()) {
if (next.getResource() != null && theClass.isAssignableFrom(next.getResource().getClass())) {
@SuppressWarnings("unchecked")
T resource = (T) next.getResource();
retVal.add(resource);
}
}
return retVal;
}
public StringDt getTitle() {
if (myTitle == null) {
myTitle = new StringDt();
}
return myTitle;
}
public IntegerDt getTotalResults() {
if (myTotalResults == null) {
myTotalResults = new IntegerDt();
}
return myTotalResults;
}
public BoundCodeDt<BundleTypeEnum> getType() {
if (myType == null) {
myType = new BoundCodeDt<BundleTypeEnum>(BundleTypeEnum.VALUESET_BINDER);
}
return myType;
}
public InstantDt getUpdated() {
InstantDt retVal = (InstantDt) getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
if (retVal == null) {
retVal= new InstantDt();
getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, retVal);
}
return retVal;
}
/**
* Returns true if this bundle contains zero entries
*/
@Override
public boolean isEmpty() {
return getEntries().isEmpty();
}
public void setCategories(TagList theCategories) {
myCategories = theCategories;
}
/*
public void setPublished(InstantDt thePublished) {
getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, thePublished);
}
/*
public void setType(BoundCodeDt<BundleTypeEnum> theType) {
myType = theType;
}
/**
* Returns the number of entries in this bundle
*/
public int size() {
return getEntries().size();
}
public List<IResource> toListOfResources() {
ArrayList<IResource> retVal = new ArrayList<IResource>();
for (BundleEntry next : getEntries()) {
if (next.getResource() != null) {
retVal.add(next.getResource());
}
}
return retVal;
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
b.append(getEntries().size() + " entries");
b.append("id", getId());
return b.toString();
}
public static Bundle withResources(List<IResource> theResources, FhirContext theContext, String theServerBase) {
Bundle retVal = new Bundle();
for (IResource next : theResources) {
retVal.addResource(next, theContext, theServerBase);
}
return retVal;
}
public static Bundle withSingleResource(IResource theResource) {
Bundle retVal = new Bundle();
retVal.addEntry().setResource(theResource);
return retVal;
}
}

View File

@ -130,6 +130,7 @@ public class ExtensionDt extends BaseIdentifiableElement implements ICompositeDa
myModifier = theModifier;
}
@Override
public ExtensionDt setUrl(String theUrl) {
myUrl = theUrl != null ? new StringDt(theUrl) : myUrl;
return this;
@ -140,6 +141,7 @@ public class ExtensionDt extends BaseIdentifiableElement implements ICompositeDa
return this;
}
@Override
public ExtensionDt setValue(IBaseDatatype theValue) {
myValue = theValue;
return this;

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.model.api;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -34,8 +35,10 @@ public interface IQueryParameterAnd<T extends IQueryParameterOr<?>> {
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
* @param theContext TODO
* @param theParamName TODO
*/
public void setValuesAsQueryTokens(List<QualifiedParamList> theParameters) throws InvalidRequestException;
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters) throws InvalidRequestException;
/**
*

View File

@ -22,11 +22,12 @@ package ca.uhn.fhir.model.api;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.method.QualifiedParamList;
public interface IQueryParameterOr<T extends IQueryParameterType> {
public void setValuesAsQueryTokens(QualifiedParamList theParameters);
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, QualifiedParamList theParameters);
public List<T> getValuesAsQueryTokens();

View File

@ -31,7 +31,8 @@ public interface IQueryParameterType {
* See FHIR specification <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search
* SearchParameter Types</a> for information on the <b>token</b> format
* </p>
*
* @param theContext TODO
* @param theParamName TODO
* @param theQualifier
* The parameter name qualifier that accompanied this value. For example, if the complete query was
* <code>http://foo?name:exact=John</code>, qualifier would be ":exact"
@ -39,7 +40,7 @@ public interface IQueryParameterType {
* The actual parameter value. For example, if the complete query was
* <code>http://foo?name:exact=John</code>, the value would be "John"
*/
public void setValueAsQueryToken(String theQualifier, String theValue);
public void setValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theValue);
/**
* Returns a representation of this parameter's value as it will be represented "over the wire". In other

View File

@ -206,7 +206,7 @@ public class Tag extends BaseElement implements IElement, IBaseCoding {
@Override
public IBaseCoding setCode(String theTerm) {
setTerm(myTerm);
setTerm(theTerm);
return this;
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.model.api;
package ca.uhn.fhir.model.api.annotation;
/*
* #%L
@ -25,11 +25,15 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Field annotation for fields which are bound to a given valueset
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.FIELD})
public @interface CodeableConceptElement {
Class<? extends ICodeEnum> type();
@Target(value = { ElementType.FIELD })
public @interface Binding {
/**
* The canonical URL of the valueset
*/
String valueSet();
}

View File

@ -1,6 +1,4 @@
package ca.uhn.fhir.model.api;
import ca.uhn.fhir.util.CoverageIgnore;
package ca.uhn.fhir.model.api.annotation;
/*
* #%L
@ -22,24 +20,19 @@ import ca.uhn.fhir.util.CoverageIgnore;
* #L%
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Represents a FHIR resource path specification, e.g.
* <code>Patient.gender.coding</code>
* <p>
* Note on equality: This class uses the {@link PathSpecification#setValue(String) value}
* as the single item used to provide {@link #hashCode()} and {@link #equals(Object)}.
* </p>
*
* @deprecated {@link Include} should be used instead
* This annotation may be used on a resource type to specify an order for
* the child names. This annotation is intended for situations where the
* class hierarchy makes it impossible to specify the order using only
* the {@link Child#order()} property
*/
@CoverageIgnore
@Deprecated
public class PathSpecification extends Include {
public PathSpecification(String theInclude) {
super(theInclude);
}
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.TYPE})
public @interface ChildOrder {
String[] names();
}

View File

@ -94,7 +94,7 @@ public abstract class BaseCodingDt extends BaseIdentifiableElement implements IC
* {@inheritDoc}
*/
@Override
public void setValueAsQueryToken(String theQualifier, String theParameter) {
public void setValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theParameter) {
int barIndex = ParameterUtil.nonEscapedIndexOf(theParameter, '|');
if (barIndex != -1) {
setSystem(theParameter.substring(0, barIndex));

View File

@ -104,7 +104,7 @@ public abstract class BaseIdentifierDt extends BaseIdentifiableElement implement
* {@inheritDoc}
*/
@Override
public void setValueAsQueryToken(String theQualifier, String theParameter) {
public void setValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theParameter) {
int barIndex = ParameterUtil.nonEscapedIndexOf(theParameter, '|');
if (barIndex != -1) {
setSystem(theParameter.substring(0, barIndex));

View File

@ -51,7 +51,7 @@ public abstract class BaseQuantityDt extends BaseIdentifiableElement implements
@Override
public void setValueAsQueryToken(String theQualifier, String theValue) {
public void setValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theValue) {
getComparatorElement().setValue(null);
setCode( null);
setSystem(null);

View File

@ -1,125 +0,0 @@
package ca.uhn.fhir.model.dstu.valueset;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.util.CoverageIgnore;
@CoverageIgnore
public enum SecurityEventObjectSensitivityEnum {
;
/**
* Identifier for this Value Set:
* http://hl7.org/fhir/vs/security-event-sensitivity
*/
public static final String VALUESET_IDENTIFIER = "http://hl7.org/fhir/vs/security-event-sensitivity";
/**
* Name for this Value Set:
* Security Event Object Sensitivity
*/
public static final String VALUESET_NAME = "Security Event Object Sensitivity";
private static Map<String, SecurityEventObjectSensitivityEnum> CODE_TO_ENUM = new HashMap<String, SecurityEventObjectSensitivityEnum>();
private static Map<String, Map<String, SecurityEventObjectSensitivityEnum>> SYSTEM_TO_CODE_TO_ENUM = new HashMap<String, Map<String, SecurityEventObjectSensitivityEnum>>();
private final String myCode;
private final String mySystem;
static {
for (SecurityEventObjectSensitivityEnum next : SecurityEventObjectSensitivityEnum.values()) {
CODE_TO_ENUM.put(next.getCode(), next);
if (!SYSTEM_TO_CODE_TO_ENUM.containsKey(next.getSystem())) {
SYSTEM_TO_CODE_TO_ENUM.put(next.getSystem(), new HashMap<String, SecurityEventObjectSensitivityEnum>());
}
SYSTEM_TO_CODE_TO_ENUM.get(next.getSystem()).put(next.getCode(), next);
}
}
/**
* Returns the code associated with this enumerated value
*/
public String getCode() {
return myCode;
}
/**
* Returns the code system associated with this enumerated value
*/
public String getSystem() {
return mySystem;
}
/**
* Returns the enumerated value associated with this code
*/
public SecurityEventObjectSensitivityEnum forCode(String theCode) {
SecurityEventObjectSensitivityEnum retVal = CODE_TO_ENUM.get(theCode);
return retVal;
}
/**
* Converts codes to their respective enumerated values
*/
public static final IValueSetEnumBinder<SecurityEventObjectSensitivityEnum> VALUESET_BINDER = new IValueSetEnumBinder<SecurityEventObjectSensitivityEnum>() {
@Override
public String toCodeString(SecurityEventObjectSensitivityEnum theEnum) {
return theEnum.getCode();
}
@Override
public String toSystemString(SecurityEventObjectSensitivityEnum theEnum) {
return theEnum.getSystem();
}
@Override
public SecurityEventObjectSensitivityEnum fromCodeString(String theCodeString) {
return CODE_TO_ENUM.get(theCodeString);
}
@Override
public SecurityEventObjectSensitivityEnum fromCodeString(String theCodeString, String theSystemString) {
Map<String, SecurityEventObjectSensitivityEnum> map = SYSTEM_TO_CODE_TO_ENUM.get(theSystemString);
if (map == null) {
return null;
}
return map.get(theCodeString);
}
};
/**
* Constructor
*/
SecurityEventObjectSensitivityEnum(String theCode, String theSystem) {
myCode = theCode;
mySystem = theSystem;
}
}

View File

@ -1,153 +0,0 @@
package ca.uhn.fhir.model.dstu.valueset;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
import ca.uhn.fhir.util.CoverageIgnore;
@CoverageIgnore
public enum SecurityEventObjectTypeEnum {
/**
* Code Value: <b>1</b>
*
* Person.
*/
PERSON("1", "http://hl7.org/fhir/object-type"),
/**
* Code Value: <b>2</b>
*
* System Object.
*/
SYSTEM_OBJECT("2", "http://hl7.org/fhir/object-type"),
/**
* Code Value: <b>3</b>
*
* Organization.
*/
ORGANIZATION("3", "http://hl7.org/fhir/object-type"),
/**
* Code Value: <b>4</b>
*
* Other.
*/
OTHER("4", "http://hl7.org/fhir/object-type"),
;
/**
* Identifier for this Value Set:
* http://hl7.org/fhir/vs/object-type
*/
public static final String VALUESET_IDENTIFIER = "http://hl7.org/fhir/vs/object-type";
/**
* Name for this Value Set:
* SecurityEventObjectType
*/
public static final String VALUESET_NAME = "SecurityEventObjectType";
private static Map<String, SecurityEventObjectTypeEnum> CODE_TO_ENUM = new HashMap<String, SecurityEventObjectTypeEnum>();
private static Map<String, Map<String, SecurityEventObjectTypeEnum>> SYSTEM_TO_CODE_TO_ENUM = new HashMap<String, Map<String, SecurityEventObjectTypeEnum>>();
private final String myCode;
private final String mySystem;
static {
for (SecurityEventObjectTypeEnum next : SecurityEventObjectTypeEnum.values()) {
CODE_TO_ENUM.put(next.getCode(), next);
if (!SYSTEM_TO_CODE_TO_ENUM.containsKey(next.getSystem())) {
SYSTEM_TO_CODE_TO_ENUM.put(next.getSystem(), new HashMap<String, SecurityEventObjectTypeEnum>());
}
SYSTEM_TO_CODE_TO_ENUM.get(next.getSystem()).put(next.getCode(), next);
}
}
/**
* Returns the code associated with this enumerated value
*/
public String getCode() {
return myCode;
}
/**
* Returns the code system associated with this enumerated value
*/
public String getSystem() {
return mySystem;
}
/**
* Returns the enumerated value associated with this code
*/
public SecurityEventObjectTypeEnum forCode(String theCode) {
SecurityEventObjectTypeEnum retVal = CODE_TO_ENUM.get(theCode);
return retVal;
}
/**
* Converts codes to their respective enumerated values
*/
public static final IValueSetEnumBinder<SecurityEventObjectTypeEnum> VALUESET_BINDER = new IValueSetEnumBinder<SecurityEventObjectTypeEnum>() {
@Override
public String toCodeString(SecurityEventObjectTypeEnum theEnum) {
return theEnum.getCode();
}
@Override
public String toSystemString(SecurityEventObjectTypeEnum theEnum) {
return theEnum.getSystem();
}
@Override
public SecurityEventObjectTypeEnum fromCodeString(String theCodeString) {
return CODE_TO_ENUM.get(theCodeString);
}
@Override
public SecurityEventObjectTypeEnum fromCodeString(String theCodeString, String theSystemString) {
Map<String, SecurityEventObjectTypeEnum> map = SYSTEM_TO_CODE_TO_ENUM.get(theSystemString);
if (map == null) {
return null;
}
return map.get(theCodeString);
}
};
/**
* Constructor
*/
SecurityEventObjectTypeEnum(String theCode, String theSystem) {
myCode = theCode;
mySystem = theSystem;
}
}

View File

@ -19,22 +19,15 @@ package ca.uhn.fhir.model.primitive;
* limitations under the License.
* #L%
*/
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.DAY;
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.MILLI;
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.MONTH;
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.SECOND;
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.YEAR;
import java.text.ParseException;
import java.util.ArrayList;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
@ -44,52 +37,14 @@ import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
static final long NANOS_PER_MILLIS = 1000000L;
static final long NANOS_PER_SECOND = 1000000000L;
/*
* Add any new formatters to the static block below!!
*/
private static final List<FastDateFormat> ourFormatters;
private static final Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}");
private static final Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy");
private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd");
private static final FastDateFormat ourYearMonthDayNoDashesFormat = FastDateFormat.getInstance("yyyyMMdd");
private static final Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}");
private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
private static final FastDateFormat ourYearMonthDayTimeMilliFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
private static final FastDateFormat ourYearMonthDayTimeMilliUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeMilliZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
private static final FastDateFormat ourYearMonthDayTimeUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM");
private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM");
private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM);
private static final FastDateFormat ourHumanDateFormat = FastDateFormat.getDateInstance(FastDateFormat.MEDIUM);
private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
private static final FastDateFormat ourYearMonthDayTimeMinsFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm");
private static final FastDateFormat ourYearMonthDayTimeMinsZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mmZZ");
private static final FastDateFormat ourHumanDateTimeFormat = FastDateFormat.getDateTimeInstance(FastDateFormat.MEDIUM, FastDateFormat.MEDIUM);
static {
ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>();
formatters.add(ourYearFormat);
formatters.add(ourYearMonthDayFormat);
formatters.add(ourYearMonthDayNoDashesFormat);
formatters.add(ourYearMonthDayTimeFormat);
formatters.add(ourYearMonthDayTimeMilliFormat);
formatters.add(ourYearMonthDayTimeUTCZFormat);
formatters.add(ourYearMonthDayTimeMilliUTCZFormat);
formatters.add(ourYearMonthDayTimeMilliZoneFormat);
formatters.add(ourYearMonthDayTimeZoneFormat);
formatters.add(ourYearMonthFormat);
formatters.add(ourYearMonthNoDashesFormat);
ourFormatters = Collections.unmodifiableList(formatters);
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeDt.class);
private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND;
private String myFractionalSeconds;
private TemporalPrecisionEnum myPrecision = null;
private TimeZone myTimeZone;
private boolean myTimeZoneZulu = false;
@ -154,33 +109,53 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
}
cal.setTime(theValue);
switch (myPrecision) {
case DAY:
return ourYearMonthDayFormat.format(cal);
case MONTH:
return ourYearMonthFormat.format(cal);
case YEAR:
return ourYearFormat.format(cal);
case MINUTE:
if (myTimeZoneZulu) {
return ourYearMonthDayTimeMinsFormat.format(cal) + "Z";
} else {
return ourYearMonthDayTimeMinsZoneFormat.format(cal);
}
case SECOND:
if (myTimeZoneZulu) {
return ourYearMonthDayTimeFormat.format(cal) + "Z";
} else {
return ourYearMonthDayTimeZoneFormat.format(cal);
}
case MILLI:
if (myTimeZoneZulu) {
return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
} else {
return ourYearMonthDayTimeMilliZoneFormat.format(cal);
StringBuilder b = new StringBuilder();
leftPadWithZeros(cal.get(Calendar.YEAR), 4, b);
if (myPrecision.ordinal() > TemporalPrecisionEnum.YEAR.ordinal()) {
b.append('-');
leftPadWithZeros(cal.get(Calendar.MONTH) + 1, 2, b);
if (myPrecision.ordinal() > TemporalPrecisionEnum.MONTH.ordinal()) {
b.append('-');
leftPadWithZeros(cal.get(Calendar.DATE), 2, b);
if (myPrecision.ordinal() > TemporalPrecisionEnum.DAY.ordinal()) {
b.append('T');
leftPadWithZeros(cal.get(Calendar.HOUR_OF_DAY), 2, b);
b.append(':');
leftPadWithZeros(cal.get(Calendar.MINUTE), 2, b);
if (myPrecision.ordinal() > TemporalPrecisionEnum.MINUTE.ordinal()) {
b.append(':');
leftPadWithZeros(cal.get(Calendar.SECOND), 2, b);
if (myPrecision.ordinal() > TemporalPrecisionEnum.SECOND.ordinal()) {
b.append('.');
b.append(myFractionalSeconds);
for (int i = myFractionalSeconds.length(); i < 3; i++) {
b.append('0');
}
}
}
if (myTimeZoneZulu) {
b.append('Z');
} else if (myTimeZone != null) {
int offset = myTimeZone.getOffset(theValue.getTime());
if (offset >= 0) {
b.append('+');
} else {
b.append('-');
offset = Math.abs(offset);
}
int hoursOffset = (int) (offset / DateUtils.MILLIS_PER_HOUR);
leftPadWithZeros(hoursOffset, 2, b);
b.append(':');
int minutesOffset = (int) (offset % DateUtils.MILLIS_PER_HOUR);
minutesOffset = (int) (minutesOffset / DateUtils.MILLIS_PER_MINUTE);
leftPadWithZeros(minutesOffset, 2, b);
}
}
}
}
throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);
return b.toString();
}
}
@ -189,6 +164,20 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
*/
protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype();
private int getOffsetIndex(String theValueString) {
int plusIndex = theValueString.indexOf('+', 16);
int minusIndex = theValueString.indexOf('-', 16);
int zIndex = theValueString.indexOf('Z', 16);
int retVal = Math.max(Math.max(plusIndex, minusIndex), zIndex);
if (retVal == -1) {
return -1;
}
if ((retVal - 2) != (plusIndex + minusIndex + zIndex)) {
throwBadDateFormat(theValueString);
}
return retVal;
}
/**
* Gets the precision for this datatype (using the default for the given type if not set)
*
@ -202,28 +191,31 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
}
/**
* Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was supplied.
* Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was
* supplied.
*/
public TimeZone getTimeZone() {
if (myTimeZoneZulu) {
return TimeZone.getTimeZone("Z");
}
return myTimeZone;
}
private boolean hasOffset(String theValue) {
boolean inTime = false;
for (int i = 0; i < theValue.length(); i++) {
switch (theValue.charAt(i)) {
case 'T':
inTime = true;
break;
case '+':
case '-':
if (inTime) {
return true;
}
break;
}
/**
* Returns the value of this object as a {@link GregorianCalendar}
*/
public GregorianCalendar getValueAsCalendar() {
if (getValue() == null) {
return null;
}
return false;
GregorianCalendar cal;
if (getTimeZone() != null) {
cal = new GregorianCalendar(getTimeZone());
} else {
cal = new GregorianCalendar();
}
cal.setTime(getValue());
return cal;
}
/**
@ -231,6 +223,9 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
*/
abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
/**
* Returns true if the timezone is set to GMT-0:00 (Z)
*/
public boolean isTimeZoneZulu() {
return myTimeZoneZulu;
}
@ -246,101 +241,136 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
return DateUtils.isSameDay(new Date(), getValue());
}
private void leftPadWithZeros(int theInteger, int theLength, StringBuilder theTarget) {
String string = Integer.toString(theInteger);
for (int i = string.length(); i < theLength; i++) {
theTarget.append('0');
}
theTarget.append(string);
}
@Override
protected Date parse(String theValue) throws DataFormatException {
try {
if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
if (!isPrecisionAllowed(YEAR)) {
ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
}
setPrecision(YEAR);
clearTimeZone();
return ((ourYearFormat).parse(theValue));
} else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
// Eg. 198401 (allow this just to be lenient)
if (!isPrecisionAllowed(MONTH)) {
ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthNoDashesFormat).parse(theValue));
} else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
// E.g. 1984-01 (this is valid according to the spec)
if (!isPrecisionAllowed(MONTH)) {
ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
}
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthFormat).parse(theValue));
} else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
// Eg. 19840101 (allow this just to be lenient)
if (!isPrecisionAllowed(DAY)) {
ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayNoDashesFormat).parse(theValue));
} else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
// E.g. 1984-01-01 (this is valid according to the spec)
if (!isPrecisionAllowed(DAY)) {
ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayFormat).parse(theValue));
} else if (theValue.length() >= 18) { // date and time with possible time zone
char timeSeparator = theValue.charAt(10);
if (timeSeparator != 'T') {
throw new DataFormatException("Invalid date/time string: " + theValue);
}
int dotIndex = theValue.indexOf('.', 18);
boolean hasMillis = dotIndex > -1;
Calendar cal = new GregorianCalendar(0, 0, 0);
cal.setTimeZone(TimeZone.getDefault());
String value = theValue;
boolean fractionalSecondsSet = false;
if (!hasMillis && !isPrecisionAllowed(SECOND)) {
ourLog.debug("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
} else if (hasMillis && !isPrecisionAllowed(MILLI)) {
ourLog.debug("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
}
Date retVal;
if (hasMillis) {
try {
if (hasOffset(theValue)) {
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z")) {
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
} else {
retVal = ourYearMonthDayTimeMilliFormat.parse(theValue);
}
} catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.MILLI);
} else {
try {
if (hasOffset(theValue)) {
retVal = ourYearMonthDayTimeZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z")) {
retVal = ourYearMonthDayTimeUTCZFormat.parse(theValue);
} else {
retVal = ourYearMonthDayTimeFormat.parse(theValue);
}
} catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.SECOND);
}
return retVal;
} else {
throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
}
} catch (ParseException e) {
throw new DataFormatException("Invalid date string (" + e.getMessage() + "): " + theValue);
if (value.length() > 0 && (value.charAt(0) == ' ' || value.charAt(value.length() - 1) == ' ')) {
value = value.trim();
}
int length = value.length();
if (length == 0) {
return null;
}
if (length < 4) {
throwBadDateFormat(value);
}
TemporalPrecisionEnum precision = null;
cal.set(Calendar.YEAR, parseInt(value, value.substring(0, 4), 0, 9999));
precision = TemporalPrecisionEnum.YEAR;
if (length > 4) {
validateCharAtIndexIs(value, 4, '-');
validateLengthIsAtLeast(value, 7);
int monthVal = parseInt(value, value.substring(5, 7), 1, 12) - 1;
cal.set(Calendar.MONTH, monthVal);
precision = TemporalPrecisionEnum.MONTH;
if (length > 7) {
validateCharAtIndexIs(value, 7, '-');
validateLengthIsAtLeast(value, 10);
cal.set(Calendar.DATE, 1); // for some reason getActualMaximum works incorrectly if date isn't set
int actualMaximum = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
cal.set(Calendar.DAY_OF_MONTH, parseInt(value, value.substring(8, 10), 1, actualMaximum));
precision = TemporalPrecisionEnum.DAY;
if (length > 10) {
validateLengthIsAtLeast(value, 17);
validateCharAtIndexIs(value, 10, 'T'); // yyyy-mm-ddThh:mm:ss
int offsetIdx = getOffsetIndex(value);
String time;
if (offsetIdx == -1) {
//throwBadDateFormat(theValue);
// No offset - should this be an error?
time = value.substring(11);
} else {
time = value.substring(11, offsetIdx);
String offsetString = value.substring(offsetIdx);
setTimeZone(value, offsetString);
cal.setTimeZone(getTimeZone());
}
int timeLength = time.length();
validateCharAtIndexIs(value, 13, ':');
cal.set(Calendar.HOUR_OF_DAY, parseInt(value, value.substring(11, 13), 0, 23));
cal.set(Calendar.MINUTE, parseInt(value, value.substring(14, 16), 0, 59));
precision = TemporalPrecisionEnum.MINUTE;
if (timeLength > 5) {
validateLengthIsAtLeast(value, 19);
validateCharAtIndexIs(value, 16, ':'); // yyyy-mm-ddThh:mm:ss
cal.set(Calendar.SECOND, parseInt(value, value.substring(17, 19), 0, 59));
precision = TemporalPrecisionEnum.SECOND;
if (timeLength > 8) {
validateCharAtIndexIs(value, 19, '.'); // yyyy-mm-ddThh:mm:ss.SSSS
validateLengthIsAtLeast(value, 20);
int endIndex = getOffsetIndex(value);
if (endIndex == -1) {
endIndex = value.length();
}
int millis;
String millisString;
if (endIndex > 23) {
myFractionalSeconds = value.substring(20, endIndex);
fractionalSecondsSet = true;
endIndex = 23;
millisString = value.substring(20, endIndex);
millis = parseInt(value, millisString, 0, 999);
} else {
millisString = value.substring(20, endIndex);
millis = parseInt(value, millisString, 0, 999);
myFractionalSeconds = millisString;
fractionalSecondsSet = true;
}
if (millisString.length() == 1) {
millis = millis * 100;
} else if (millisString.length() == 2) {
millis = millis * 10;
}
cal.set(Calendar.MILLISECOND, millis);
precision = TemporalPrecisionEnum.MILLI;
}
}
}
} else {
cal.set(Calendar.DATE, 1);
}
} else {
cal.set(Calendar.DATE, 1);
}
if (fractionalSecondsSet == false) {
myFractionalSeconds = "";
}
setPrecision(precision);
return cal.getTime();
}
private int parseInt(String theValue, String theSubstring, int theLowerBound, int theUpperBound) {
int retVal = 0;
try {
retVal = Integer.parseInt(theSubstring);
} catch (NumberFormatException e) {
throwBadDateFormat(theValue);
}
if (retVal < theLowerBound || retVal > theUpperBound) {
throwBadDateFormat(theValue);
}
return retVal;
}
/**
@ -348,26 +378,33 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
*
* @throws DataFormatException
*/
public void setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
public BaseDateTimeDt setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
if (thePrecision == null) {
throw new NullPointerException("Precision may not be null");
}
myPrecision = thePrecision;
updateStringValue();
return this;
}
private BaseDateTimeDt setTimeZone(String theValueString, boolean hasMillis) {
clearTimeZone();
int timeZoneStart = 19;
if (hasMillis)
timeZoneStart += 4;
if (theValueString.endsWith("Z")) {
private BaseDateTimeDt setTimeZone(String theWholeValue, String theValue) {
if (isBlank(theValue)) {
throwBadDateFormat(theWholeValue);
} else if (theValue.charAt(0) == 'Z') {
clearTimeZone();
setTimeZoneZulu(true);
} else if (theValueString.indexOf("GMT", timeZoneStart) != -1) {
setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart)));
} else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) {
setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart)));
} else if (theValue.length() != 6) {
throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\"");
} else if (theValue.charAt(3) != ':' || !(theValue.charAt(0) == '+' || theValue.charAt(0) == '-')) {
throwBadDateFormat(theWholeValue, "Timezone offset must be in the form \"Z\", \"-HH:mm\", or \"+HH:mm\"");
} else {
parseInt(theWholeValue, theValue.substring(1, 3), 0, 23);
parseInt(theWholeValue, theValue.substring(4, 6), 0, 59);
clearTimeZone();
setTimeZone(TimeZone.getTimeZone("GMT" + theValue));
}
return this;
}
@ -384,17 +421,19 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
}
/**
* Sets the value for this type using the given Java Date object as the time, and using the default precision for this datatype, as well as the local timezone as determined by the local operating
* Sets the value for this type using the given Java Date object as the time, and using the default precision for
* this datatype (unless the precision is already set), as well as the local timezone as determined by the local operating
* system. Both of these properties may be modified in subsequent calls if neccesary.
*/
@Override
public BaseDateTimeDt setValue(Date theValue) {
setValue(theValue, getDefaultPrecisionForDatatype());
setValue(theValue, getPrecision());
return this;
}
/**
* Sets the value for this type using the given Java Date object as the time, and using the specified precision, as well as the local timezone as determined by the local operating system. Both of
* Sets the value for this type using the given Java Date object as the time, and using the specified precision, as
* well as the local timezone as determined by the local operating system. Both of
* these properties may be modified in subsequent calls if neccesary.
*
* @param theValue
@ -404,8 +443,20 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
* @throws DataFormatException
*/
public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws DataFormatException {
setTimeZone(TimeZone.getDefault());
if (getTimeZone() == null) {
setTimeZone(TimeZone.getDefault());
}
myPrecision = thePrecision;
myFractionalSeconds = "";
if (theValue != null) {
long millis = theValue.getTime() % 1000;
if (millis < 0) {
// This is for times before 1970 (see bug #444)
millis = 1000 + millis;
}
String fractionalSeconds = Integer.toString((int) millis);
myFractionalSeconds = StringUtils.leftPad(fractionalSeconds, 3, '0');
}
super.setValue(theValue);
}
@ -415,11 +466,21 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
super.setValueAsString(theValue);
}
private void throwBadDateFormat(String theValue) {
throw new DataFormatException("Invalid date/time format: \"" + theValue + "\"");
}
private void throwBadDateFormat(String theValue, String theMesssage) {
throw new DataFormatException("Invalid date/time format: \"" + theValue + "\": " + theMesssage);
}
/**
* Returns a human readable version of this date/time using the system local format.
* <p>
* <b>Note on time zones:</b> This method renders the value using the time zone that is contained within the value. For example, if this date object contains the value "2012-01-05T12:00:00-08:00",
* the human display will be rendered as "12:00:00" even if the application is being executed on a system in a different time zone. If this behaviour is not what you want, use
* <b>Note on time zones:</b> This method renders the value using the time zone that is contained within the value.
* For example, if this date object contains the value "2012-01-05T12:00:00-08:00",
* the human display will be rendered as "12:00:00" even if the application is being executed on a system in a
* different time zone. If this behaviour is not what you want, use
* {@link #toHumanDisplayLocalTimezone()} instead.
* </p>
*/
@ -441,7 +502,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
}
/**
* Returns a human readable version of this date/time using the system local format, converted to the local timezone if neccesary.
* Returns a human readable version of this date/time using the system local format, converted to the local timezone
* if neccesary.
*
* @see #toHumanDisplay() for a method which does not convert the time to the local timezone before rendering it.
*/
@ -458,11 +520,201 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
}
}
private void validateCharAtIndexIs(String theValue, int theIndex, char theChar) {
if (theValue.charAt(theIndex) != theChar) {
throwBadDateFormat(theValue, "Expected character '" + theChar + "' at index " + theIndex + " but found " + theValue.charAt(theIndex));
}
}
private void validateLengthIsAtLeast(String theValue, int theLength) {
if (theValue.length() < theLength) {
throwBadDateFormat(theValue);
}
}
/**
* For unit tests only
* Returns the year, e.g. 2015
*/
static List<FastDateFormat> getFormatters() {
return ourFormatters;
public Integer getYear() {
return getFieldValue(Calendar.YEAR);
}
/**
* Returns the month with 0-index, e.g. 0=January
*/
public Integer getMonth() {
return getFieldValue(Calendar.MONTH);
}
/**
* Returns the month with 1-index, e.g. 1=the first day of the month
*/
public Integer getDay() {
return getFieldValue(Calendar.DAY_OF_MONTH);
}
/**
* Returns the hour of the day in a 24h clock, e.g. 13=1pm
*/
public Integer getHour() {
return getFieldValue(Calendar.HOUR_OF_DAY);
}
/**
* Returns the minute of the hour in the range 0-59
*/
public Integer getMinute() {
return getFieldValue(Calendar.MINUTE);
}
/**
* Returns the second of the minute in the range 0-59
*/
public Integer getSecond() {
return getFieldValue(Calendar.SECOND);
}
/**
* Returns the milliseconds within the current second.
* <p>
* Note that this method returns the
* same value as {@link #getNanos()} but with less precision.
* </p>
*/
public Integer getMillis() {
return getFieldValue(Calendar.MILLISECOND);
}
/**
* Returns the nanoseconds within the current second
* <p>
* Note that this method returns the
* same value as {@link #getMillis()} but with more precision.
* </p>
*/
public Long getNanos() {
if (isBlank(myFractionalSeconds)) {
return null;
}
String retVal = StringUtils.rightPad(myFractionalSeconds, 9, '0');
retVal = retVal.substring(0, 9);
return Long.parseLong(retVal);
}
/**
* Sets the year, e.g. 2015
*/
public BaseDateTimeDt setYear(int theYear) {
setFieldValue(Calendar.YEAR, theYear, null, 0, 9999);
return this;
}
/**
* Sets the month with 0-index, e.g. 0=January
*/
public BaseDateTimeDt setMonth(int theMonth) {
setFieldValue(Calendar.MONTH, theMonth, null, 0, 11);
return this;
}
/**
* Sets the month with 1-index, e.g. 1=the first day of the month
*/
public BaseDateTimeDt setDay(int theDay) {
setFieldValue(Calendar.DAY_OF_MONTH, theDay, null, 0, 31);
return this;
}
/**
* Sets the hour of the day in a 24h clock, e.g. 13=1pm
*/
public BaseDateTimeDt setHour(int theHour) {
setFieldValue(Calendar.HOUR_OF_DAY, theHour, null, 0, 23);
return this;
}
/**
* Sets the minute of the hour in the range 0-59
*/
public BaseDateTimeDt setMinute(int theMinute) {
setFieldValue(Calendar.MINUTE, theMinute, null, 0, 59);
return this;
}
/**
* Sets the second of the minute in the range 0-59
*/
public BaseDateTimeDt setSecond(int theSecond) {
setFieldValue(Calendar.SECOND, theSecond, null, 0, 59);
return this;
}
/**
* Sets the milliseconds within the current second.
* <p>
* Note that this method sets the
* same value as {@link #setNanos(long)} but with less precision.
* </p>
*/
public BaseDateTimeDt setMillis(int theMillis) {
setFieldValue(Calendar.MILLISECOND, theMillis, null, 0, 999);
return this;
}
/**
* Sets the nanoseconds within the current second
* <p>
* Note that this method sets the
* same value as {@link #setMillis(int)} but with more precision.
* </p>
*/
public BaseDateTimeDt setNanos(long theNanos) {
validateValueInRange(theNanos, 0, NANOS_PER_SECOND-1);
String fractionalSeconds = StringUtils.leftPad(Long.toString(theNanos), 9, '0');
// Strip trailing 0s
for (int i = fractionalSeconds.length(); i > 0; i--) {
if (fractionalSeconds.charAt(i-1) != '0') {
fractionalSeconds = fractionalSeconds.substring(0, i);
break;
}
}
int millis = (int)(theNanos / NANOS_PER_MILLIS);
setFieldValue(Calendar.MILLISECOND, millis, fractionalSeconds, 0, 999);
return this;
}
private void setFieldValue(int theField, int theValue, String theFractionalSeconds, int theMinimum, int theMaximum) {
validateValueInRange(theValue, theMinimum, theMaximum);
Calendar cal;
if (getValue() == null) {
cal = new GregorianCalendar(0, 0, 0);
} else {
cal = getValueAsCalendar();
}
if (theField != -1) {
cal.set(theField, theValue);
}
if (theFractionalSeconds != null) {
myFractionalSeconds = theFractionalSeconds;
} else if (theField == Calendar.MILLISECOND) {
myFractionalSeconds = StringUtils.leftPad(Integer.toString(theValue), 3, '0');
}
super.setValue(cal.getTime());
}
private void validateValueInRange(long theValue, long theMinimum, long theMaximum) {
if (theValue < theMinimum || theValue > theMaximum) {
throw new IllegalArgumentException("Value " + theValue + " is not between allowable range: " + theMinimum + " - " + theMaximum);
}
}
private Integer getFieldValue(int theField) {
if (getValue() == null) {
return null;
}
Calendar cal = getValueAsCalendar();
return cal.get(theField);
}
}

View File

@ -27,7 +27,7 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
@DatatypeDef(name = "code", profileOf=StringDt.class)
public class CodeDt extends BasePrimitive<String> implements ICodedDatatype, Comparable<CodeDt> {
public class CodeDt extends BasePrimitive<String> implements Comparable<CodeDt> {
/**
* Constructor

View File

@ -78,7 +78,7 @@ public class DecimalDt extends BasePrimitive<BigDecimal> implements Comparable<D
if (getValue() == null && theObj.getValue() == null) {
return 0;
}
if (getValue() != null && theObj.getValue() == null) {
if (getValue() != null && (theObj == null || theObj.getValue() == null)) {
return 1;
}
if (getValue() == null && theObj.getValue() != null) {

View File

@ -185,5 +185,4 @@ public class InstantDt extends BaseDateTimeDt {
return DEFAULT_PRECISION;
}
}

View File

@ -88,7 +88,7 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
* {@inheritDoc}
*/
@Override
public void setValueAsQueryToken(String theQualifier, String theValue) {
public void setValueAsQueryToken(FhirContext theContext, String theParamName, String theQualifier, String theValue) {
setValue(theValue);
}

View File

@ -52,6 +52,7 @@ public class TimeDt extends StringDt implements IQueryParameterType {
*/
@SimpleSetter
public TimeDt(@SimpleSetter.Parameter(name = "theString") String theValue) {
this();
setValue(theValue);
}

View File

@ -32,22 +32,6 @@ import ca.uhn.fhir.model.api.annotation.SimpleSetter;
@DatatypeDef(name = "uri")
public class UriDt extends BasePrimitive<String> {
/**
* Creates a new UriDt instance which uses the given OID as the content (and prepends "urn:oid:" to the OID string
* in the value of the newly created UriDt, per the FHIR specification).
*
* @param theOid
* The OID to use (<code>null</code> is acceptable and will result in a UriDt instance with a
* <code>null</code> value)
* @return A new UriDt instance
*/
public static UriDt fromOid(String theOid) {
if (theOid == null) {
return new UriDt();
}
return new UriDt("urn:oid:" + theOid);
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UriDt.class);
/**
@ -119,18 +103,15 @@ public class UriDt extends BasePrimitive<String> {
URI retVal;
try {
retVal = new URI(theValue).normalize();
} catch (URISyntaxException e1) {
ourLog.debug("Failed to normalize URL '{}', message was: {}", theValue, e1.toString());
String urlString = retVal.toString();
if (urlString.endsWith("/") && urlString.length() > 1) {
retVal = new URI(urlString.substring(0, urlString.length() - 1));
}
} catch (URISyntaxException e) {
ourLog.debug("Failed to normalize URL '{}', message was: {}", theValue, e.toString());
return theValue;
}
String urlString = retVal.toString();
if (urlString.endsWith("/") && urlString.length() > 1) {
try {
retVal = new URI(urlString.substring(0, urlString.length() - 1));
} catch (URISyntaxException e) {
ourLog.debug("Failed to normalize URL '{}', message was: {}", urlString, e.toString());
}
}
return retVal.toASCIIString();
}
@ -139,4 +120,20 @@ public class UriDt extends BasePrimitive<String> {
return theValue;
}
/**
* Creates a new UriDt instance which uses the given OID as the content (and prepends "urn:oid:" to the OID string
* in the value of the newly created UriDt, per the FHIR specification).
*
* @param theOid
* The OID to use (<code>null</code> is acceptable and will result in a UriDt instance with a
* <code>null</code> value)
* @return A new UriDt instance
*/
public static UriDt fromOid(String theOid) {
if (theOid == null) {
return new UriDt();
}
return new UriDt("urn:oid:" + theOid);
}
}

View File

@ -162,10 +162,13 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
int firstTagIndex = value.indexOf("<", hasProcessingInstruction ? 1 : 0);
if (firstTagIndex != -1) {
int firstTagEnd = value.indexOf(">", firstTagIndex);
int firstSlash = value.indexOf("/", firstTagIndex);
if (firstTagEnd != -1) {
String firstTag = value.substring(firstTagIndex, firstTagEnd);
if (!firstTag.contains(" xmlns")) {
value = value.substring(0, firstTagEnd) + DECL_XMLNS + value.substring(firstTagEnd);
if (firstSlash > firstTagEnd) {
String firstTag = value.substring(firstTagIndex, firstTagEnd);
if (!firstTag.contains(" xmlns")) {
value = value.substring(0, firstTagEnd) + DECL_XMLNS + value.substring(firstTagEnd);
}
}
}
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.narrative;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -25,54 +25,45 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.thymeleaf.Arguments;
import org.thymeleaf.Configuration;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.cache.AlwaysValidCacheEntryValidity;
import org.thymeleaf.cache.ICacheEntryValidity;
import org.thymeleaf.context.Context;
import org.thymeleaf.dom.Document;
import org.thymeleaf.dom.Element;
import org.thymeleaf.dom.Node;
import org.thymeleaf.exceptions.TemplateInputException;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
import org.thymeleaf.resourceresolver.IResourceResolver;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
import org.thymeleaf.processor.element.IElementTagStructureHandler;
import org.thymeleaf.standard.StandardDialect;
import org.thymeleaf.standard.expression.IStandardExpression;
import org.thymeleaf.standard.expression.IStandardExpressionParser;
import org.thymeleaf.standard.expression.StandardExpressions;
import org.thymeleaf.templatemode.StandardTemplateModeHandlers;
import org.thymeleaf.templateparser.xmlsax.XhtmlAndHtml5NonValidatingSAXTemplateParser;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.DefaultTemplateResolver;
import org.thymeleaf.templateresource.ITemplateResource;
import org.thymeleaf.templateresource.StringTemplateResource;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.Constants;
public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGenerator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseThymeleafNarrativeGenerator.class);
private static final XhtmlAndHtml5NonValidatingSAXTemplateParser PARSER = new XhtmlAndHtml5NonValidatingSAXTemplateParser(1);
private Configuration myThymeleafConfig;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseThymeleafNarrativeGenerator.class);
private boolean myApplyDefaultDatatypeTemplates = true;
private HashMap<Class<?>, String> myClassToName;
private boolean myCleanWhitespace = true;
private boolean myIgnoreFailures = true;
@ -81,16 +72,13 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
private HashMap<String, String> myNameToNarrativeTemplate;
private TemplateEngine myProfileTemplateEngine;
/**
* Constructor
*/
public BaseThymeleafNarrativeGenerator() {
myThymeleafConfig = new Configuration();
myThymeleafConfig.addTemplateResolver(new ClassLoaderTemplateResolver());
myThymeleafConfig.addMessageResolver(new StandardMessageResolver());
myThymeleafConfig.setTemplateModeHandlers(StandardTemplateModeHandlers.ALL_TEMPLATE_MODE_HANDLERS);
myThymeleafConfig.initialize();
super();
}
@Override
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
if (!myInitialized) {
@ -105,15 +93,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
name = theContext.getResourceDefinition(theResource).getName().toLowerCase();
}
if (name == null) {
if (name == null || !myNameToNarrativeTemplate.containsKey(name)) {
if (myIgnoreMissingTemplates) {
ourLog.debug("No narrative template available for resorce: {}", name);
try {
theNarrative.setDivAsString("<div>No narrative template available for resource : " + name + "</div>");
} catch (Exception e) {
// last resort..
}
theNarrative.setStatusAsString("empty");
return;
} else {
throw new DataFormatException("No narrative template for class " + theResource.getClass().getCanonicalName());
@ -132,7 +114,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
result = cleanWhitespace(result);
ourLog.trace("Post-whitespace cleaning: ", result);
}
if (isBlank(result)) {
return;
}
@ -156,20 +138,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
}
protected abstract List<String> getPropertyFile();
private Document getXhtmlDOMFor(final Reader source) {
final Configuration configuration1 = myThymeleafConfig;
try {
return PARSER.parseTemplate(configuration1, "input", source);
} catch (final Exception e) {
throw new TemplateInputException("Exception during parsing of source", e);
}
}
private synchronized void initialize(FhirContext theContext) {
private synchronized void initialize(final FhirContext theContext) {
if (myInitialized) {
return;
}
@ -195,15 +166,18 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
{
myProfileTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new ProfileResourceResolver());
ProfileResourceResolver resolver = new ProfileResourceResolver();
myProfileTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
HashSet<IProcessor> additionalProcessors = new HashSet<IProcessor>();
additionalProcessors.add(new NarrativeAttributeProcessor(theContext));
dialect.setAdditionalProcessors(additionalProcessors);
StandardDialect dialect = new StandardDialect() {
@Override
public Set<IProcessor> getProcessors(String theDialectPrefix) {
Set<IProcessor> retVal = super.getProcessors(theDialectPrefix);
retVal.add(new NarrativeAttributeProcessor(theContext, theDialectPrefix));
return retVal;
}
};
myProfileTemplateEngine.setDialect(dialect);
myProfileTemplateEngine.initialize();
}
myInitialized = true;
@ -214,7 +188,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
* before it is returned.
* <p>
* Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g.
* "\n \n ") will be trimmed to a single space.
* "\n \n ") will be trimmed to a single space.
* </p>
*/
public boolean isCleanWhitespace() {
@ -259,7 +233,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
if (StringUtils.isNotBlank(narrativeName)) {
String narrative = IOUtils.toString(loadResource(narrativeName));
String narrative = IOUtils.toString(loadResource(narrativeName), Constants.CHARSET_UTF8);
myNameToNarrativeTemplate.put(name, narrative);
}
@ -292,7 +266,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName);
if (StringUtils.isNotBlank(narrativeName)) {
String narrative = IOUtils.toString(loadResource(narrativeName));
String narrative = IOUtils.toString(loadResource(narrativeName), Constants.CHARSET_UTF8);
myNameToNarrativeTemplate.put(name, narrative);
}
continue;
@ -332,7 +306,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
* before it is returned.
* <p>
* Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g.
* "\n \n ") will be trimmed to a single space.
* "\n \n ") will be trimmed to a single space.
* </p>
*/
public void setCleanWhitespace(boolean theCleanWhitespace) {
@ -398,7 +372,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
char char4 = Character.toLowerCase((i + 4 < theResult.length()) ? theResult.charAt(i + 4) : ' ');
if (char1 == 'p' && char2 == 'r' && char3 == 'e') {
inPre = true;
} else if (char1 == '/' && char2 == 'p' && char3 == 'r'&&char4=='e') {
} else if (char1 == '/' && char2 == 'p' && char3 == 'r' && char4 == 'e') {
inPre = false;
}
}
@ -416,37 +390,27 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
return b.toString();
}
public class NarrativeAttributeProcessor extends AbstractAttrProcessor {
public class NarrativeAttributeProcessor extends AbstractAttributeTagProcessor {
private FhirContext myContext;
protected NarrativeAttributeProcessor(FhirContext theContext) {
super("narrative");
protected NarrativeAttributeProcessor(FhirContext theContext, String theDialectPrefix) {
super(TemplateMode.XML, theDialectPrefix, null, false, "narrative", true, 0, true);
myContext = theContext;
}
@Override
public int getPrecedence() {
return 0;
}
@SuppressWarnings("unchecked")
@Override
protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) {
final String attributeValue = theElement.getAttributeValue(theAttributeName);
protected void doProcess(ITemplateContext theContext, IProcessableElementTag theTag, AttributeName theAttributeName, String theAttributeValue, IElementTagStructureHandler theStructureHandler) {
IEngineConfiguration configuration = theContext.getConfiguration();
IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
final Configuration configuration = theArguments.getConfiguration();
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
final Object value = expression.execute(configuration, theArguments);
theElement.removeAttribute(theAttributeName);
theElement.clearChildren();
final IStandardExpression expression = expressionParser.parseExpression(theContext, theAttributeValue);
final Object value = expression.execute(theContext);
if (value == null) {
return ProcessorResult.ok();
return;
}
Context context = new Context();
@ -460,7 +424,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
name = myClassToName.get(nextClass);
nextClass = nextClass.getSuperclass();
} while (name == null && nextClass.equals(Object.class) == false);
if (name == null) {
if (value instanceof IBaseResource) {
name = myContext.getResourceDefinition((Class<? extends IBaseResource>) value).getName();
@ -485,7 +449,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
if (name == null) {
if (myIgnoreMissingTemplates) {
ourLog.debug("No narrative template available for type: {}", value.getClass());
return ProcessorResult.ok();
return;
} else {
throw new DataFormatException("No narrative template for class " + value.getClass());
}
@ -493,29 +457,112 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
String result = myProfileTemplateEngine.process(name, context);
String trim = result.trim();
if (!isBlank(trim + "AAA")) {
Document dom = getXhtmlDOMFor(new StringReader(trim));
Element firstChild = (Element) dom.getFirstChild();
for (int i = 0; i < firstChild.getChildren().size(); i++) {
Node next = firstChild.getChildren().get(i);
if (i == 0 && firstChild.getChildren().size() == 1) {
if (next instanceof org.thymeleaf.dom.Text) {
org.thymeleaf.dom.Text nextText = (org.thymeleaf.dom.Text) next;
nextText.setContent(nextText.getContent().trim());
}
}
theElement.addChild(next);
}
}
return ProcessorResult.ok();
theStructureHandler.setBody(trim, true);
}
}
// public class NarrativeAttributeProcessor extends AbstractAttributeTagProcessor {
//
// private FhirContext myContext;
//
// protected NarrativeAttributeProcessor(FhirContext theContext) {
// super()
// myContext = theContext;
// }
//
// @Override
// public int getPrecedence() {
// return 0;
// }
//
// @SuppressWarnings("unchecked")
// @Override
// protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) {
// final String attributeValue = theElement.getAttributeValue(theAttributeName);
//
// final Configuration configuration = theArguments.getConfiguration();
// final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
//
// final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue);
// final Object value = expression.execute(configuration, theArguments);
//
// theElement.removeAttribute(theAttributeName);
// theElement.clearChildren();
//
// if (value == null) {
// return ProcessorResult.ok();
// }
//
// Context context = new Context();
// context.setVariable("fhirVersion", myContext.getVersion().getVersion().name());
// context.setVariable("resource", value);
//
// String name = null;
// if (value != null) {
// Class<? extends Object> nextClass = value.getClass();
// do {
// name = myClassToName.get(nextClass);
// nextClass = nextClass.getSuperclass();
// } while (name == null && nextClass.equals(Object.class) == false);
//
// if (name == null) {
// if (value instanceof IBaseResource) {
// name = myContext.getResourceDefinition((Class<? extends IBaseResource>) value).getName();
// } else if (value instanceof IDatatype) {
// name = value.getClass().getSimpleName();
// name = name.substring(0, name.length() - 2);
// } else if (value instanceof IBaseDatatype) {
// name = value.getClass().getSimpleName();
// if (name.endsWith("Type")) {
// name = name.substring(0, name.length() - 4);
// }
// } else {
// throw new DataFormatException("Don't know how to determine name for type: " + value.getClass());
// }
// name = name.toLowerCase();
// if (!myNameToNarrativeTemplate.containsKey(name)) {
// name = null;
// }
// }
// }
//
// if (name == null) {
// if (myIgnoreMissingTemplates) {
// ourLog.debug("No narrative template available for type: {}", value.getClass());
// return ProcessorResult.ok();
// } else {
// throw new DataFormatException("No narrative template for class " + value.getClass());
// }
// }
//
// String result = myProfileTemplateEngine.process(name, context);
// String trim = result.trim();
// if (!isBlank(trim + "AAA")) {
// Document dom = getXhtmlDOMFor(new StringReader(trim));
//
// Element firstChild = (Element) dom.getFirstChild();
// for (int i = 0; i < firstChild.getChildren().size(); i++) {
// Node next = firstChild.getChildren().get(i);
// if (i == 0 && firstChild.getChildren().size() == 1) {
// if (next instanceof org.thymeleaf.dom.Text) {
// org.thymeleaf.dom.Text nextText = (org.thymeleaf.dom.Text) next;
// nextText.setContent(nextText.getContent().trim());
// }
// }
// theElement.addChild(next);
// }
//
// }
//
//
// return ProcessorResult.ok();
// }
//
// }
// public String generateString(Patient theValue) {
//
// Context context = new Context();
@ -529,21 +576,30 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
// return result;
// }
private final class ProfileResourceResolver implements IResourceResolver {
private final class ProfileResourceResolver extends DefaultTemplateResolver {
@Override
public String getName() {
return getClass().getCanonicalName();
protected boolean computeResolvable(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
String template = myNameToNarrativeTemplate.get(theTemplate);
return template != null;
}
@Override
public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) {
String template = myNameToNarrativeTemplate.get(theName);
if (template == null) {
ourLog.info("No narative template for resource profile: {}", theName);
return new ReaderInputStream(new StringReader(""));
}
return new ReaderInputStream(new StringReader(template));
protected TemplateMode computeTemplateMode(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
return TemplateMode.XML;
}
@Override
protected ITemplateResource computeTemplateResource(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
String template = myNameToNarrativeTemplate.get(theTemplate);
return new StringTemplateResource(template);
}
@Override
protected ICacheEntryValidity computeValidity(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
return AlwaysValidCacheEntryValidity.INSTANCE;
}
}
}

View File

@ -1,55 +0,0 @@
package ca.uhn.fhir.narrative;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.thymeleaf.TemplateProcessingParameters;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.TemplateResolution;
public class ThymeleafResolver implements ITemplateResolver {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ThymeleafResolver.class);
@Override
public String getName() {
return "";
}
@Override
public Integer getOrder() {
return 0;
}
@Override
public void initialize() {
// nothing
}
@Override
public TemplateResolution resolveTemplate(TemplateProcessingParameters theTemplateProcessingParameters) {
String templateName = theTemplateProcessingParameters.getTemplateName();
ourLog.info("Resolving template: {}", templateName);
return null;
}
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.parser;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -27,7 +27,10 @@ import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -65,7 +68,6 @@ import ca.uhn.fhir.context.RuntimeChildContainedResources;
import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IIdentifiableElement;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
@ -81,6 +83,7 @@ public abstract class BaseParser implements IParser {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
private ContainedResources myContainedResources;
private FhirContext myContext;
private Set<String> myDontEncodeElements;
private boolean myDontEncodeElementsIncludesStars;
@ -92,9 +95,10 @@ public abstract class BaseParser implements IParser {
private boolean myOmitResourceId;
private List<Class<? extends IBaseResource>> myPreferTypes;
private String myServerBaseUrl;
private boolean myStripVersionsFromReferences = true;
private Boolean myStripVersionsFromReferences;
private boolean mySummaryMode;
private boolean mySuppressNarratives;
private Set<String> myDontStripVersionsFromReferencesAtPaths;
/**
* Constructor
@ -265,7 +269,7 @@ public abstract class BaseParser implements IParser {
myContainedResources = contained;
}
private String determineReferenceText(IBaseReference theRef) {
private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
IIdType ref = theRef.getReferenceElement();
if (isBlank(ref.getIdPart())) {
String reference = ref.getValue();
@ -281,13 +285,17 @@ public abstract class BaseParser implements IParser {
IIdType refId = theRef.getResource().getIdElement();
if (refId != null) {
if (refId.hasIdPart()) {
if (!refId.hasResourceType()) {
refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
}
if (isStripVersionsFromReferences()) {
reference = refId.toVersionless().getValue();
} else {
if (refId.getValue().startsWith("urn:")) {
reference = refId.getValue();
} else {
if (!refId.hasResourceType()) {
refId = refId.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
}
if (isStripVersionsFromReferences(theCompositeChildElement)) {
reference = refId.toVersionless().getValue();
} else {
reference = refId.getValue();
}
}
}
}
@ -299,13 +307,13 @@ public abstract class BaseParser implements IParser {
ref = ref.withResourceType(myContext.getResourceDefinition(theRef.getResource()).getName());
}
if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
if (isStripVersionsFromReferences()) {
if (isStripVersionsFromReferences(theCompositeChildElement)) {
return ref.toUnqualifiedVersionless().getValue();
} else {
return ref.toUnqualified().getValue();
}
} else {
if (isStripVersionsFromReferences()) {
if (isStripVersionsFromReferences(theCompositeChildElement)) {
return ref.toVersionless().getValue();
} else {
return ref.getValue();
@ -314,19 +322,29 @@ public abstract class BaseParser implements IParser {
}
}
protected String determineResourceBaseUrl(String bundleBaseUrl, BundleEntry theEntry) {
IResource resource = theEntry.getResource();
if (resource == null) {
return null;
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
if (stripVersionsFromReferences != null) {
return stripVersionsFromReferences;
}
String resourceBaseUrl = null;
if (resource.getId() != null && resource.getId().hasBaseUrl()) {
if (!resource.getId().getBaseUrl().equals(bundleBaseUrl)) {
resourceBaseUrl = resource.getId().getBaseUrl();
if (myContext.getParserOptions().isStripVersionsFromReferences() == false) {
return false;
}
Set<String> dontStripVersionsFromReferencesAtPaths = myDontStripVersionsFromReferencesAtPaths;
if (dontStripVersionsFromReferencesAtPaths != null) {
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
return false;
}
}
return resourceBaseUrl;
dontStripVersionsFromReferencesAtPaths = myContext.getParserOptions().getDontStripVersionsFromReferencesAtPaths();
if (dontStripVersionsFromReferencesAtPaths.isEmpty() == false && theCompositeChildElement.anyPathMatches(dontStripVersionsFromReferencesAtPaths)) {
return false;
}
return true;
}
protected abstract void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException;
@ -374,7 +392,8 @@ public abstract class BaseParser implements IParser {
Validate.notNull(theWriter, "theWriter can not be null");
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
throw new IllegalArgumentException("This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
throw new IllegalArgumentException(
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
}
doEncodeResourceToWriter(theResource, theWriter);
@ -410,6 +429,51 @@ public abstract class BaseParser implements IParser {
return retVal;
}
@SuppressWarnings("unchecked")
ChildNameAndDef getChildNameAndDef(BaseRuntimeChildDefinition theChild, IBase theValue) {
Class<? extends IBase> type = theValue.getClass();
String childName = theChild.getChildNameByDatatype(type);
BaseRuntimeElementDefinition<?> childDef = theChild.getChildElementDefinitionByDatatype(type);
if (childDef == null) {
// if (theValue instanceof IBaseExtension) {
// return null;
// }
/*
* For RI structures Enumeration class, this replaces the child def
* with the "code" one. This is messy, and presumably there is a better
* way..
*/
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type);
if (elementDef.getName().equals("code")) {
Class<? extends IBase> type2 = myContext.getElementDefinition("code").getImplementingClass();
childDef = theChild.getChildElementDefinitionByDatatype(type2);
childName = theChild.getChildNameByDatatype(type2);
}
// See possibly the user has extended a built-in type without
// declaring it anywhere, as in XmlParserDstu3Test#testEncodeUndeclaredBlock
if (childDef == null) {
Class<?> nextSuperType = theValue.getClass();
while (IBase.class.isAssignableFrom(nextSuperType) && childDef == null) {
if (Modifier.isAbstract(nextSuperType.getModifiers()) == false) {
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition((Class<? extends IBase>) nextSuperType);
Class<?> nextChildType = def.getImplementingClass();
childDef = theChild.getChildElementDefinitionByDatatype((Class<? extends IBase>) nextChildType);
childName = theChild.getChildNameByDatatype((Class<? extends IBase>) nextChildType);
}
nextSuperType = nextSuperType.getSuperclass();
}
}
if (childDef == null) {
throwExceptionForUnknownChildType(theChild, type);
}
}
return new ChildNameAndDef(childName, childDef);
}
protected String getCompositeElementId(IBase theElement) {
String elementId = null;
if (!(theElement instanceof IBaseResource)) {
@ -519,7 +583,8 @@ public abstract class BaseParser implements IParser {
}
protected boolean isChildContained(BaseRuntimeElementDefinition<?> childDef, boolean theIncludedResource) {
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false && theIncludedResource == false;
return (childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCES || childDef.getChildType() == ChildTypeEnum.CONTAINED_RESOURCE_LIST) && getContainedResources().isEmpty() == false
&& theIncludedResource == false;
}
@Override
@ -528,7 +593,7 @@ public abstract class BaseParser implements IParser {
}
@Override
public boolean isStripVersionsFromReferences() {
public Boolean getStripVersionsFromReferences() {
return myStripVersionsFromReferences;
}
@ -564,13 +629,15 @@ public abstract class BaseParser implements IParser {
@Override
public <T extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
/*
* We do this so that the context can verify that the structure is for
* the correct FHIR version
*/
if (theResourceType != null) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResourceType);
if (def.getStructureVersion() != myContext.getVersion().getVersion()) {
throw new IllegalArgumentException("This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not parse a structure for version " + def.getStructureVersion());
}
myContext.getResourceDefinition(theResourceType);
}
// Actually do the parse
T retVal = doParseResource(theResourceType, theReader);
RuntimeResourceDefinition def = myContext.getResourceDefinition(retVal);
@ -637,16 +704,17 @@ public abstract class BaseParser implements IParser {
return parseTagList(new StringReader(theString));
}
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition metaChildUncast, IBaseResource theResource, List<? extends IBase> theValues) {
protected List<? extends IBase> preProcessValues(BaseRuntimeChildDefinition theMetaChildUncast, IBaseResource theResource, List<? extends IBase> theValues,
CompositeChildElement theCompositeChildElement) {
if (myContext.getVersion().getVersion().isRi()) {
/*
* If we're encoding the meta tag, we do some massaging of the meta values before
* encoding. Buf if there is no meta element at all, we create one since we're possibly going to be
* encoding. But if there is no meta element at all, we create one since we're possibly going to be
* adding things to it
*/
if (theValues.isEmpty() && metaChildUncast.getElementName().equals("meta")) {
BaseRuntimeElementDefinition<?> metaChild = metaChildUncast.getChildByName("meta");
if (theValues.isEmpty() && theMetaChildUncast.getElementName().equals("meta")) {
BaseRuntimeElementDefinition<?> metaChild = theMetaChildUncast.getChildByName("meta");
if (IBaseMetaType.class.isAssignableFrom(metaChild.getImplementingClass())) {
IBaseMetaType newType = (IBaseMetaType) metaChild.newInstance();
theValues = Collections.singletonList(newType);
@ -706,7 +774,7 @@ public abstract class BaseParser implements IParser {
*/
if (next instanceof IBaseReference) {
IBaseReference nextRef = (IBaseReference) next;
String refText = determineReferenceText(nextRef);
String refText = determineReferenceText(nextRef, theCompositeChildElement);
if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
if (retVal == theValues) {
@ -794,11 +862,39 @@ public abstract class BaseParser implements IParser {
}
@Override
public IParser setStripVersionsFromReferences(boolean theStripVersionsFromReferences) {
public IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences) {
myStripVersionsFromReferences = theStripVersionsFromReferences;
return this;
}
@Override
public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
if (thePaths == null) {
setDontStripVersionsFromReferencesAtPaths((List<String>) null);
} else {
setDontStripVersionsFromReferencesAtPaths(Arrays.asList(thePaths));
}
return this;
}
@SuppressWarnings("unchecked")
@Override
public IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths) {
if (thePaths == null) {
myDontStripVersionsFromReferencesAtPaths = Collections.emptySet();
} else if (thePaths instanceof HashSet) {
myDontStripVersionsFromReferencesAtPaths = (Set<String>) ((HashSet<String>) thePaths).clone();
} else {
myDontStripVersionsFromReferencesAtPaths = new HashSet<String>(thePaths);
}
return this;
}
@Override
public Set<String> getDontStripVersionsFromReferencesAtPaths() {
return myDontStripVersionsFromReferencesAtPaths;
}
@Override
public IParser setSummaryMode(boolean theSummaryMode) {
mySummaryMode = theSummaryMode;
@ -812,7 +908,7 @@ public abstract class BaseParser implements IParser {
}
protected boolean shouldAddSubsettedTag() {
return isSummaryMode() || isSuppressNarratives();
return isSummaryMode() || isSuppressNarratives() || getEncodeElements() != null;
}
protected boolean shouldEncodeResourceId(IBaseResource theResource) {
@ -897,6 +993,26 @@ public abstract class BaseParser implements IParser {
return false;
}
class ChildNameAndDef {
private final BaseRuntimeElementDefinition<?> myChildDef;
private final String myChildName;
public ChildNameAndDef(String theChildName, BaseRuntimeElementDefinition<?> theChildDef) {
myChildName = theChildName;
myChildDef = theChildDef;
}
public BaseRuntimeElementDefinition<?> getChildDef() {
return myChildDef;
}
public String getChildName() {
return myChildName;
}
}
protected class CompositeChildElement {
private final BaseRuntimeChildDefinition myDef;
private final CompositeChildElement myParent;
@ -920,6 +1036,34 @@ public abstract class BaseParser implements IParser {
}
public boolean anyPathMatches(Set<String> thePaths) {
StringBuilder b = new StringBuilder();
addParent(this, b);
String path = b.toString();
return thePaths.contains(path);
}
private void addParent(CompositeChildElement theParent, StringBuilder theB) {
if (theParent != null) {
if (theParent.myResDef != null) {
theB.append(theParent.myResDef.getName());
return;
}
if (theParent.myParent != null) {
addParent(theParent.myParent, theB);
}
if (theParent.myDef != null) {
if (theB.length() > 0) {
theB.append('.');
}
theB.append(theParent.myDef.getElementName());
}
}
}
public CompositeChildElement(RuntimeResourceDefinition theResDef) {
myResDef = theResDef;
myDef = null;
@ -931,25 +1075,27 @@ public abstract class BaseParser implements IParser {
StringBuilder b = new StringBuilder();
b.append(myResDef.getName());
return b;
} else {
} else if (myParent != null) {
StringBuilder b = myParent.buildPath();
if (b != null && myDef != null) {
b.append('.');
b.append(myDef.getElementName());
}
return b;
} else {
return null;
}
}
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
return checkIfPathMatches(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true);
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true);
}
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
return checkIfPathMatches(thePathBuilder, theStarPass, null, myDontEncodeElements, false);
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, null, myDontEncodeElements, false);
}
private boolean checkIfPathMatches(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) {
private boolean checkIfPathMatchesForEncoding(StringBuilder thePathBuilder, boolean theStarPass, Set<String> theResourceTypes, Set<String> theElements, boolean theCheckingForWhitelist) {
if (myResDef != null) {
if (theResourceTypes != null) {
if (!theResourceTypes.contains(myResDef.getName())) {
@ -1019,12 +1165,12 @@ public abstract class BaseParser implements IParser {
retVal = !checkIfParentShouldNotBeEncodedAndBuildPath(new StringBuilder(), true);
}
}
// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
// if (myDef.getMin() > 0) {
// retVal = true;
// }
// }
// if (retVal == false && myEncodeElements.contains("*.(mandatory)")) {
// if (myDef.getMin() > 0) {
// retVal = true;
// }
// }
return retVal;
}
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.parser;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -24,9 +24,14 @@ package ca.uhn.fhir.parser;
* Adapter implementation with NOP implementations of all {@link IParserErrorHandler} methods.
*/
public class ErrorHandlerAdapter implements IParserErrorHandler {
@Override
public void unknownElement(IParseLocation theLocation, String theElementName) {
public void containedResourceWithNoId(IParseLocation theLocation) {
// NOP
}
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
// NOP
}
@ -36,7 +41,17 @@ public class ErrorHandlerAdapter implements IParserErrorHandler {
}
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
public void unknownElement(IParseLocation theLocation, String theElementName) {
// NOP
}
@Override
public void unknownReference(IParseLocation theLocation, String theReference) {
// NOP
}
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
// NOP
}

View File

@ -0,0 +1,101 @@
package ca.uhn.fhir.parser;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.parser.json.JsonLikeStructure;
import ca.uhn.fhir.parser.json.JsonLikeWriter;
/**
* An extension to the parser interface that is implemented by parsers that understand a generalized form of
* JSON data. This generalized form uses Map-like, List-like, and scalar elements to construct resources.
* <p>
* Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or
* every message being parsed/encoded.
* </p>
*/
public interface IJsonLikeParser extends IParser {
void encodeBundleToJsonLikeWriter(Bundle theBundle, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException;
void encodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theJsonLikeWriter) throws IOException, DataFormatException;
void encodeTagListToJsonLikeWriter(TagList theTagList, JsonLikeWriter theJsonLikeWriter) throws IOException;
/**
* Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you should use
* {@link #parseResource(Class, JsonLikeStructure)} with the Bundle class found in the
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> package instead.
*/
<T extends IBaseResource> Bundle parseBundle(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure);
/**
* Parse a DSTU1 style Atom Bundle. Note that as of DSTU2, Bundle is a resource so you should use
* {@link #parseResource(Class, JsonLikeStructure)} with the Bundle class found in the
* <code>ca.uhn.hapi.fhir.model.[version].resource</code> package instead.
*/
Bundle parseBundle(JsonLikeStructure theJsonLikeStructure) throws DataFormatException;
/**
* Parses a resource from a JSON-like data structure
*
* @param theResourceType
* The resource type to use. This can be used to explicitly specify a class which extends a built-in type
* (e.g. a custom type extending the default Patient class)
* @param theJsonLikeStructure
* The JSON-like structure to parse
* @return A parsed resource
* @throws DataFormatException
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
<T extends IBaseResource> T parseResource(Class<T> theResourceType, JsonLikeStructure theJsonLikeStructure) throws DataFormatException;
/**
* Parses a resource from a JSON-like data structure
*
* @param theJsonLikeStructure
* The JSON-like structure to parse
* @return A parsed resource. Note that the returned object will be an instance of {@link IResource} or
* {@link IAnyResource} depending on the specific FhirContext which created this parser.
* @throws DataFormatException
* If the resource can not be parsed because the data is not recognized or invalid for any reason
*/
IBaseResource parseResource(JsonLikeStructure theJsonLikeStructure) throws DataFormatException;
/**
* Parses a tag list from a JSON-like data structure
*
* @param theJsonLikeStructure
* The JSON-like structure to parse
* @return A parsed tag list
*/
TagList parseTagList(JsonLikeStructure theJsonLikeStructure);
}

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.parser;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@ -32,6 +33,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ParserOptions;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
@ -64,7 +66,7 @@ public interface IParser {
* @return An encoded tag list
*/
String encodeTagListToString(TagList theTagList);
/**
* Encodes a tag list, as defined in the <a href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
@ -75,7 +77,7 @@ public interface IParser {
* The writer to encode to
*/
void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException;
/**
* See {@link #setEncodeElements(Set)}
*/
@ -121,9 +123,11 @@ public interface IParser {
* links. In that case, this value should be set to <code>false</code>.
*
* @return Returns the parser instance's configuration setting for stripping versions from resource references when
* encoding. Default is <code>true</code>.
* encoding. This method will retun <code>null</code> if no value is set, in which case
* the value from the {@link ParserOptions} will be used (default is <code>true</code>)
* @see ParserOptions
*/
boolean isStripVersionsFromReferences();
Boolean getStripVersionsFromReferences();
/**
* Is the parser in "summary mode"? See {@link #setSummaryMode(boolean)} for information
@ -301,21 +305,22 @@ public interface IParser {
IParser setParserErrorHandler(IParserErrorHandler theErrorHandler);
/**
* If set, when parsing resources the parser will try to use the given types when possible, in
* If set, when parsing resources the parser will try to use the given types when possible, in
* the order that they are provided (from highest to lowest priority). For example, if a custom
* type which declares to implement the Patient resource is passed in here, and the
* type which declares to implement the Patient resource is passed in here, and the
* parser is parsing a Bundle containing a Patient resource, the parser will use the given
* custom type.
* <p>
* This feature is related to, but not the same as the
* This feature is related to, but not the same as the
* {@link FhirContext#setDefaultTypeForProfile(String, Class)} feature.
* <code>setDefaultTypeForProfile</code> is used to specify a type to be used
* when a resource explicitly declares support for a given profile. This
* feature specifies a type to be used irrespective of the profile declaration
* in the metadata statement.
* </p>
* in the metadata statement.
* </p>
*
* @param thePreferTypes The preferred types, or <code>null</code>
* @param thePreferTypes
* The preferred types, or <code>null</code>
*/
void setPreferTypes(List<Class<? extends IBaseResource>> thePreferTypes);
@ -345,13 +350,17 @@ public interface IParser {
* in most situations, references from one resource to another should be to the resource by ID, not
* by ID and version. In some cases though, it may be desirable to preserve the version in resource
* links. In that case, this value should be set to <code>false</code>.
*
* <p>
* This method provides the ability to globally disable reference encoding. If finer-grained
* control is needed, use {@link #setDontStripVersionsFromReferencesAtPaths(String...)}
* </p>
* @param theStripVersionsFromReferences
* Set this to <code>false<code> to prevent the parser from removing
* resource versions from references.
* Set this to <code>false<code> to prevent the parser from removing resource versions from references (or <code>null</code> to apply the default setting from the {@link ParserOptions}
* @see #setDontStripVersionsFromReferencesAtPaths(String...)
* @see ParserOptions
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
*/
IParser setStripVersionsFromReferences(boolean theStripVersionsFromReferences);
IParser setStripVersionsFromReferences(Boolean theStripVersionsFromReferences);
/**
* If set to <code>true</code> (default is <code>false</code>) only elements marked by the FHIR specification as
@ -366,4 +375,60 @@ public interface IParser {
* values.
*/
IParser setSuppressNarratives(boolean theSuppressNarratives);
/**
* If supplied value(s), any resource references at the specified paths will have their
* resource versions encoded instead of being automatically stripped during the encoding
* process. This setting has no effect on the parsing process.
* <p>
* This method provides a finer-grained level of control than {@link #setStripVersionsFromReferences(Boolean)}
* and any paths specified by this method will be encoded even if {@link #setStripVersionsFromReferences(Boolean)}
* has been set to <code>true</code> (which is the default)
* </p>
*
* @param thePaths
* A collection of paths for which the resource versions will not be removed automatically
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
* only resource name and field names with dots separating is allowed here (no repetition
* indicators, FluentPath expressions, etc.). Set to <code>null</code> to use the value
* set in the {@link ParserOptions}
* @see #setStripVersionsFromReferences(Boolean)
* @see ParserOptions
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
*/
IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths);
/**
* If supplied value(s), any resource references at the specified paths will have their
* resource versions encoded instead of being automatically stripped during the encoding
* process. This setting has no effect on the parsing process.
* <p>
* This method provides a finer-grained level of control than {@link #setStripVersionsFromReferences(Boolean)}
* and any paths specified by this method will be encoded even if {@link #setStripVersionsFromReferences(Boolean)}
* has been set to <code>true</code> (which is the default)
* </p>
*
* @param thePaths
* A collection of paths for which the resource versions will not be removed automatically
* when serializing, e.g. "Patient.managingOrganization" or "AuditEvent.object.reference". Note that
* only resource name and field names with dots separating is allowed here (no repetition
* indicators, FluentPath expressions, etc.). Set to <code>null</code> to use the value
* set in the {@link ParserOptions}
* @see #setStripVersionsFromReferences(Boolean)
* @see ParserOptions
* @return Returns a reference to <code>this</code> parser so that method calls can be chained together
*/
IParser setDontStripVersionsFromReferencesAtPaths(Collection<String> thePaths);
/**
* Returns the value supplied to {@link IParser#setDontStripVersionsFromReferencesAtPaths(String...)}
* or <code>null</code> if no value has been set for this parser (in which case the default from
* the {@link ParserOptions} will be used}
*
* @see #setDontStripVersionsFromReferencesAtPaths(String...)
* @see #setStripVersionsFromReferences(Boolean)
* @see ParserOptions
*/
Set<String> getDontStripVersionsFromReferencesAtPaths();
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
/*
* #%L
* HAPI FHIR - Core Library
@ -10,7 +12,7 @@ package ca.uhn.fhir.parser;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -25,39 +27,82 @@ package ca.uhn.fhir.parser;
*/
public interface IParserErrorHandler {
/**
* Invoked when a contained resource is parsed that has no ID specified (and is therefore invalid)
*
* @param theLocation
* The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without
* changing the API.
* @since 2.0
*/
void containedResourceWithNoId(IParseLocation theLocation);
/**
* Resource was missing a required element
*
* @param theLocation
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theReference The actual invalid reference (e.g. "#3")
* @since 2.1
*/
void missingRequiredElement(IParseLocation theLocation, String theElementName);
/**
* Invoked when an element repetition (e.g. a second repetition of something) is found for a field
* which is non-repeating.
*
* @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API.
* @param theElementName The name of the element that was found.
* @param theLocation
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theElementName
* The name of the element that was found.
* @since 1.2
*/
void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName);
/**
* Invoked when an unknown element is found in the document.
* Invoked when an unknown element is found in the document.
*
* @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API.
* @param theAttributeName The name of the attribute that was found.
* @param theLocation
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theAttributeName
* The name of the attribute that was found.
*/
void unknownAttribute(IParseLocation theLocation, String theAttributeName);
/**
* Invoked when an unknown element is found in the document.
* Invoked when an unknown element is found in the document.
*
* @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API.
* @param theElementName The name of the element that was found.
* @param theLocation
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theElementName
* The name of the element that was found.
*/
void unknownElement(IParseLocation theLocation, String theElementName);
/**
* Resource contained a reference that could not be resolved and needs to be resolvable (e.g. because
* it is a local reference to an unknown contained resource)
*
* @param theLocation
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theReference The actual invalid reference (e.g. "#3")
* @since 2.0
*/
void unknownReference(IParseLocation theLocation, String theReference);
/**
* For now this is an empty interface. Error handling methods include a parameter of this
* type which will currently always be set to null. This interface is included here so that
* locations can be added to the API in a future release without changing the API.
*/
public interface IParseLocation {
// nothing for now
/**
* Returns the name of the parent element (the element containing the element currently being parsed)
* @since 2.1
*/
String getParentElementName();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
/*
* #%L
@ -71,4 +72,25 @@ public class LenientErrorHandler implements IParserErrorHandler {
}
}
@Override
public void containedResourceWithNoId(IParseLocation theLocation) {
if (myLogErrors) {
ourLog.warn("Resource has contained child resource with no ID");
}
}
@Override
public void unknownReference(IParseLocation theLocation, String theReference) {
if (myLogErrors) {
ourLog.warn("Resource has invalid reference: {}", theReference);
}
}
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
ourLog.warn("Resource is missing required element: {}", theElementName);
}
}
}

View File

@ -1,6 +1,4 @@
package ca.uhn.fhir.util.reflection;
import java.lang.reflect.Method;
package ca.uhn.fhir.parser;
/*
* #%L
@ -22,10 +20,23 @@ import java.lang.reflect.Method;
* #L%
*/
public interface IBeanUtils {
Method findAccessor(Class<?> theClassToIntrospect, Class<?> theTargetReturnType, String thePropertyName)
throws NoSuchFieldException;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
class ParseLocation implements IParseLocation {
private String myParentElementName;
/**
* Constructor
*/
public ParseLocation(String theParentElementName) {
super();
myParentElementName = theParentElementName;
}
@Override
public String getParentElementName() {
return myParentElementName;
}
Method findMutator(Class<?> theClassToIntrospect, Class<?> theTargetReturnType, String thePropertyName)
throws NoSuchFieldException;
}

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.parser;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -91,6 +92,7 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.IModelVisitor;
import ca.uhn.fhir.util.ReflectionUtil;
class ParserState<T> {
@ -104,6 +106,7 @@ class ParserState<T> {
private final IParser myParser;
private IBase myPreviousElement;
private BaseState myState;
private ParserState(IParser theParser, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
myParser = theParser;
myContext = theContext;
@ -128,16 +131,6 @@ class ParserState<T> {
}
}
private BaseState createResourceReferenceState(ParserState<T>.PreResourceState thePreResourceState, IBase newChildInstance) {
BaseState newState;
if (newChildInstance instanceof IBaseReference) {
newState = new ResourceReferenceStateHl7Org(thePreResourceState, (IBaseReference) newChildInstance);
} else {
newState = new ResourceReferenceStateHapi(thePreResourceState, (BaseResourceReferenceDt) newChildInstance);
}
return newState;
}
public void endingElement() throws DataFormatException {
myState.endingElement();
}
@ -159,31 +152,7 @@ class ParserState<T> {
}
private Object newContainedDt(IResource theTarget) {
Object newChildInstance;
try {
newChildInstance = theTarget.getStructureFhirVersionEnum().getVersionImplementation().getContainedType().newInstance();
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to instantiate " + myContext.getVersion().getResourceReferenceType(), e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to instantiate " + myContext.getVersion().getResourceReferenceType(), e);
}
return newChildInstance;
}
private IBase newResourceReferenceDt(IBaseResource theTarget) {
IBase newChildInstance;
try {
IFhirVersion version;
version = theTarget.getStructureFhirVersionEnum().getVersionImplementation();
newChildInstance = version.getResourceReferenceType().newInstance();
} catch (InstantiationException e) {
throw new ConfigurationException("Failed to instantiate " + myContext.getVersion().getResourceReferenceType(), e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Failed to instantiate " + myContext.getVersion().getResourceReferenceType(), e);
}
return newChildInstance;
return ReflectionUtil.newInstance(theTarget.getStructureFhirVersionEnum().getVersionImplementation().getContainedType());
}
@SuppressWarnings("unchecked")
@ -256,7 +225,8 @@ class ParserState<T> {
}
}
static ParserState<Bundle> getPreAtomInstance(IParser theParser, FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
static ParserState<Bundle> getPreAtomInstance(IParser theParser, FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler)
throws DataFormatException {
ParserState<Bundle> retVal = new ParserState<Bundle>(theParser, theContext, theJsonMode, theErrorHandler);
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
retVal.push(retVal.new PreAtomState(theResourceType));
@ -270,7 +240,8 @@ class ParserState<T> {
* @param theResourceType
* May be null
*/
static <T extends IBaseResource> ParserState<T> getPreResourceInstance(IParser theParser, Class<T> theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
static <T extends IBaseResource> ParserState<T> getPreResourceInstance(IParser theParser, Class<T> theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler)
throws DataFormatException {
ParserState<T> retVal = new ParserState<T>(theParser, theContext, theJsonMode, theErrorHandler);
if (theResourceType == null) {
if (theContext.getVersion().getVersion().isRi()) {
@ -745,11 +716,7 @@ class ParserState<T> {
} else if ("deleted-entry".equals(theLocalPart) && verifyNamespace(XmlParser.TOMBSTONES_NS, theNamespaceUri)) {
push(new AtomDeletedEntryState(myInstance, myResourceType));
} else {
if (theNamespaceUri != null) {
throw new DataFormatException("Unexpected element: {" + theNamespaceUri + "}" + theLocalPart);
} else {
throw new DataFormatException("Unexpected element: " + theLocalPart);
}
logAndSwallowUnexpectedElement(theLocalPart);
}
// TODO: handle category and DSig
@ -863,7 +830,8 @@ class ParserState<T> {
@SuppressWarnings("unused")
public void enteringNewElementExtension(StartElement theElement, String theUrlAttr, boolean theIsModifier) {
if (myPreResourceState != null && getCurrentElement() instanceof ISupportsUndeclaredExtensions) {
ExtensionDt newExtension = new ExtensionDt(theIsModifier, theUrlAttr);
ExtensionDt newExtension = new ExtensionDt(theIsModifier);
newExtension.setUrl(theUrlAttr);
ISupportsUndeclaredExtensions elem = (ISupportsUndeclaredExtensions) getCurrentElement();
elem.addUndeclaredExtension(newExtension);
ExtensionState newState = new ExtensionState(myPreResourceState, newExtension);
@ -1426,16 +1394,18 @@ class ParserState<T> {
@Override
public void wereBack() {
super.wereBack();
IResource res = (IResource) getCurrentElement();
assert res != null;
if (res.getId() == null || res.getId().isEmpty()) {
ourLog.debug("Discarding contained resource with no ID!");
// If there is no ID, we don't keep the resource because it's useless (contained resources
// need an ID to be referred to)
myErrorHandler.containedResourceWithNoId(null);
} else {
getPreResourceState().getContainedResources().put(res.getId().getValueAsString(), res);
if (!res.getId().isLocal()) {
res.setId(new IdDt('#' + res.getId().getIdPart()));
}
getPreResourceState().getContainedResources().put(res.getId().getValueAsString(), res);
}
IResource preResCurrentElement = (IResource) getPreResourceState().getCurrentElement();
@ -1470,12 +1440,12 @@ class ParserState<T> {
IBaseResource res = getCurrentElement();
assert res != null;
if (res.getIdElement() == null || res.getIdElement().isEmpty()) {
ourLog.debug("Discarding contained resource with no ID!");
// If there is no ID, we don't keep the resource because it's useless (contained resources
// need an ID to be referred to)
myErrorHandler.containedResourceWithNoId(null);
} else {
res.getIdElement().setValue('#' + res.getIdElement().getIdPart());
getPreResourceState().getContainedResources().put(res.getIdElement().getValue(), res);
if (!res.getIdElement().isLocal()) {
res.getIdElement().setValue('#' + res.getIdElement().getIdPart());
}
}
IBaseResource preResCurrentElement = getPreResourceState().getCurrentElement();
@ -1539,13 +1509,6 @@ class ParserState<T> {
push(newState);
return;
}
case RESOURCE_REF: {
IBase newChildInstance = newResourceReferenceDt(myPreResourceState.myInstance);
myDefinition.getMutator().addValue(myParentInstance, newChildInstance);
BaseState newState = createResourceReferenceState(getPreResourceState(), newChildInstance);
push(newState);
return;
}
case PRIMITIVE_XHTML:
case RESOURCE:
case RESOURCE_BLOCK:
@ -1607,7 +1570,6 @@ class ParserState<T> {
}
}
@SuppressWarnings("unchecked")
@Override
public void endingElement() {
pop();
@ -1625,7 +1587,7 @@ class ParserState<T> {
return;
}
}
/*
* This means we've found an element that doesn't exist on the structure. If the error handler doesn't throw
* an exception, swallow the element silently along with any child elements
@ -1666,13 +1628,6 @@ class ParserState<T> {
push(newState);
return;
}
case RESOURCE_REF: {
IBase newChildInstance = newResourceReferenceDt(getPreResourceState().myInstance);
child.getMutator().addValue(myInstance, newChildInstance);
BaseState newState = createResourceReferenceState(getPreResourceState(), newChildInstance);
push(newState);
return;
}
case RESOURCE_BLOCK: {
RuntimeResourceBlockDefinition blockTarget = (RuntimeResourceBlockDefinition) target;
IBase newBlockInstance = blockTarget.newInstance();
@ -1792,10 +1747,10 @@ class ParserState<T> {
}
if ("id".equals(theName)) {
if (getCurrentElement() instanceof IBaseElement) {
((IBaseElement)getCurrentElement()).setId(theValue);
((IBaseElement) getCurrentElement()).setId(theValue);
return;
} else if (getCurrentElement() instanceof IIdentifiableElement) {
((IIdentifiableElement)getCurrentElement()).setElementSpecificId(theValue);
((IIdentifiableElement) getCurrentElement()).setElementSpecificId(theValue);
return;
}
}
@ -1823,54 +1778,35 @@ class ParserState<T> {
}
BaseRuntimeElementDefinition<?> target = myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(theLocalPart);
if (target == null) {
myErrorHandler.unknownElement(null, theLocalPart);
push(new SwallowChildrenWholeState(getPreResourceState()));
return;
}
switch (target.getChildType()) {
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> compositeTarget = (BaseRuntimeElementCompositeDefinition<?>) target;
ICompositeType newChildInstance = (ICompositeType) compositeTarget.newInstance();
myExtension.setValue(newChildInstance);
ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), compositeTarget, newChildInstance);
push(newState);
return;
}
case PRIMITIVE_DATATYPE: {
RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target;
IPrimitiveType<?> newChildInstance = primitiveTarget.newInstance();
myExtension.setValue(newChildInstance);
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance);
push(newState);
return;
}
case RESOURCE_REF: {
ICompositeType newChildInstance = (ICompositeType) newResourceReferenceDt(getPreResourceState().myInstance);
myExtension.setValue(newChildInstance);
if (myContext.getVersion().getVersion().isRi()) {
ParserState<T>.ResourceReferenceStateHl7Org newState = new ResourceReferenceStateHl7Org(getPreResourceState(), (IBaseReference) newChildInstance);
push(newState);
} else {
ResourceReferenceStateHapi newState = new ResourceReferenceStateHapi(getPreResourceState(), (BaseResourceReferenceDt) newChildInstance);
if (target != null) {
switch (target.getChildType()) {
case COMPOSITE_DATATYPE: {
BaseRuntimeElementCompositeDefinition<?> compositeTarget = (BaseRuntimeElementCompositeDefinition<?>) target;
ICompositeType newChildInstance = (ICompositeType) compositeTarget.newInstance();
myExtension.setValue(newChildInstance);
ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), compositeTarget, newChildInstance);
push(newState);
return;
}
case ID_DATATYPE:
case PRIMITIVE_DATATYPE: {
RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target;
IPrimitiveType<?> newChildInstance = primitiveTarget.newInstance();
myExtension.setValue(newChildInstance);
PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance);
push(newState);
return;
}
}
return;
}
case PRIMITIVE_XHTML:
case RESOURCE:
case RESOURCE_BLOCK:
case UNDECL_EXT:
case EXTENSION_DECLARED:
case CONTAINED_RESOURCES:
case CONTAINED_RESOURCE_LIST:
case ID_DATATYPE:
case PRIMITIVE_XHTML_HL7ORG:
break;
}
}
// We hit an invalid type for the extension
myErrorHandler.unknownElement(null, theLocalPart);
push(new SwallowChildrenWholeState(getPreResourceState()));
return;
}
@Override
protected IBaseExtension<?, ?> getCurrentElement() {
return myExtension;
@ -1936,13 +1872,17 @@ class ParserState<T> {
} else if (theLocalPart.equals("profile")) {
@SuppressWarnings("unchecked")
List<IdDt> profiles = (List<IdDt>) myMap.get(ResourceMetadataKeyEnum.PROFILES);
if (profiles == null) {
profiles = new ArrayList<IdDt>();
myMap.put(ResourceMetadataKeyEnum.PROFILES, profiles);
List<IdDt> newProfiles;
if (profiles != null) {
newProfiles = new ArrayList<IdDt>(profiles.size() + 1);
newProfiles.addAll(profiles);
} else {
newProfiles = new ArrayList<IdDt>(1);
}
IdDt profile = new IdDt();
push(new PrimitiveState(getPreResourceState(), profile));
profiles.add(profile);
newProfiles.add(profile);
myMap.put(ResourceMetadataKeyEnum.PROFILES, Collections.unmodifiableList(newProfiles));
} else if (theLocalPart.equals("tag")) {
TagList tagList = (TagList) myMap.get(ResourceMetadataKeyEnum.TAG_LIST);
if (tagList == null) {
@ -2053,11 +1993,49 @@ class ParserState<T> {
@Override
public void endingElement() throws DataFormatException {
// postProcess();
stitchBundleCrossReferences();
pop();
}
protected void weaveContainedResources() {
FhirTerser terser = myContext.newTerser();
terser.visit(myInstance, new IModelVisitor() {
@Override
public void acceptElement(IBaseResource theResource, IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
if (theElement instanceof BaseResourceReferenceDt) {
BaseResourceReferenceDt nextRef = (BaseResourceReferenceDt) theElement;
String ref = nextRef.getReference().getValue();
if (isNotBlank(ref)) {
if (ref.startsWith("#")) {
IResource target = (IResource) myContainedResources.get(ref);
if (target != null) {
ourLog.debug("Resource contains local ref {} in field {}", ref, thePathToElement);
nextRef.setResource(target);
} else {
myErrorHandler.unknownReference(null, ref);
}
}
}
} else if (theElement instanceof IBaseReference) {
IBaseReference nextRef = (IBaseReference) theElement;
String ref = nextRef.getReferenceElement().getValue();
if (isNotBlank(ref)) {
if (ref.startsWith("#")) {
IBaseResource target = myContainedResources.get(ref);
if (target != null) {
ourLog.debug("Resource contains local ref {} in field {}", ref, thePathToElement);
nextRef.setResource(target);
} else {
myErrorHandler.unknownReference(null, ref);
}
}
}
}
}
});
}
@Override
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition<?> definition;
@ -2154,11 +2132,11 @@ class ParserState<T> {
}
}
}
if (wantedProfileType != null && !wantedProfileType.equals(myInstance.getClass())) {
if (myResourceType == null || myResourceType.isAssignableFrom(wantedProfileType)) {
ourLog.debug("Converting resource of type {} to type defined for profile \"{}\": {}", new Object[] {myInstance.getClass().getName(), usedProfile, wantedProfileType});
ourLog.debug("Converting resource of type {} to type defined for profile \"{}\": {}", new Object[] { myInstance.getClass().getName(), usedProfile, wantedProfileType });
/*
* This isn't the most efficient thing really.. If we want a specific
* type we just re-parse into that type. The problem is that we don't know
@ -2175,53 +2153,13 @@ class ParserState<T> {
}
}
FhirTerser terser = myContext.newTerser();
terser.visit(myInstance, new IModelVisitor() {
@Override
public void acceptElement(IBase theElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition) {
if (theElement instanceof BaseResourceReferenceDt) {
BaseResourceReferenceDt nextRef = (BaseResourceReferenceDt) theElement;
String ref = nextRef.getReference().getValue();
if (isNotBlank(ref)) {
if (ref.startsWith("#")) {
IResource target = (IResource) myContainedResources.get(ref.substring(1));
if (target != null) {
nextRef.setResource(target);
} else {
ourLog.warn("Resource contains unknown local ref: " + ref);
}
}
}
} else if (theElement instanceof IBaseReference) {
IBaseReference nextRef = (IBaseReference) theElement;
String ref = nextRef.getReferenceElement().getValue();
if (isNotBlank(ref)) {
if (ref.startsWith("#")) {
IBaseResource target = myContainedResources.get(ref.substring(1));
if (target != null) {
nextRef.setResource(target);
} else {
ourLog.warn("Resource contains unknown local ref: " + ref);
}
}
}
}
}
@Override
public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List<String> thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition<?> theDefinition, ExtensionDt theNextExt) {
acceptElement(theNextExt.getValue(), null, null, null);
}
});
populateTarget();
}
private void stitchBundleCrossReferences() {
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
if (bundle) {
/*
* Stitch together resource references
*/
@ -2259,7 +2197,7 @@ class ParserState<T> {
}
}
}
}
}
@ -2288,14 +2226,15 @@ class ParserState<T> {
assert theResourceType == null || IResource.class.isAssignableFrom(theResourceType);
}
// @Override
// public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
// super.enteringNewElement(theNamespaceUri, theLocalPart);
// populateTarget();
// }
// @Override
// public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
// super.enteringNewElement(theNamespaceUri, theLocalPart);
// populateTarget();
// }
@Override
protected void populateTarget() {
weaveContainedResources();
if (myEntry != null) {
myEntry.setResource((IResource) getCurrentElement());
}
@ -2304,7 +2243,6 @@ class ParserState<T> {
}
}
@SuppressWarnings("unchecked")
@Override
public void wereBack() {
super.wereBack();
@ -2342,12 +2280,12 @@ class ParserState<T> {
@Override
protected void populateTarget() {
weaveContainedResources();
if (myMutator != null) {
myMutator.setValue(myTarget, getCurrentElement());
}
}
@SuppressWarnings("unchecked")
@Override
public void wereBack() {
super.wereBack();
@ -2460,151 +2398,6 @@ class ParserState<T> {
}
private class ResourceReferenceStateHapi extends BaseState {
private BaseResourceReferenceDt myInstance;
private ResourceReferenceSubState mySubState;
public ResourceReferenceStateHapi(PreResourceState thePreResourceState, BaseResourceReferenceDt theInstance) {
super(thePreResourceState);
myInstance = theInstance;
mySubState = ResourceReferenceSubState.INITIAL;
}
@Override
public void attributeValue(String theName, String theValue) throws DataFormatException {
if (!"value".equals(theName)) {
return;
}
switch (mySubState) {
case DISPLAY:
myInstance.getDisplayElement().setValue(theValue);
break;
case INITIAL:
throw new DataFormatException("Unexpected attribute: " + theValue);
case REFERENCE:
myInstance.getReference().setValue(theValue);
break;
}
}
@Override
public void endingElement() {
switch (mySubState) {
case INITIAL:
pop();
break;
case DISPLAY:
case REFERENCE:
mySubState = ResourceReferenceSubState.INITIAL;
}
}
@Override
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
switch (mySubState) {
case INITIAL:
if ("display".equals(theLocalPart)) {
mySubState = ResourceReferenceSubState.DISPLAY;
break;
} else if ("reference".equals(theLocalPart)) {
mySubState = ResourceReferenceSubState.REFERENCE;
break;
} else if ("resource".equals(theLocalPart)) {
mySubState = ResourceReferenceSubState.REFERENCE;
break;
}
//$FALL-THROUGH$
case DISPLAY:
case REFERENCE:
throw new DataFormatException("Unexpected element: " + theLocalPart);
}
}
@Override
protected IElement getCurrentElement() {
return myInstance;
}
}
private class ResourceReferenceStateHl7Org extends BaseState {
private IBaseReference myInstance;
private ResourceReferenceSubState mySubState;
public ResourceReferenceStateHl7Org(PreResourceState thePreResourceState, IBaseReference theInstance) {
super(thePreResourceState);
myInstance = theInstance;
mySubState = ResourceReferenceSubState.INITIAL;
}
@Override
public void attributeValue(String theName, String theValue) throws DataFormatException {
if (!"value".equals(theName)) {
return;
}
switch (mySubState) {
case DISPLAY:
myInstance.setDisplay(theValue);
break;
case INITIAL:
throw new DataFormatException("Unexpected attribute: " + theValue);
case REFERENCE:
myInstance.setReference(theValue);
break;
}
}
@Override
public void endingElement() {
switch (mySubState) {
case INITIAL:
pop();
break;
case DISPLAY:
case REFERENCE:
mySubState = ResourceReferenceSubState.INITIAL;
}
}
@Override
public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException {
switch (mySubState) {
case INITIAL:
if ("display".equals(theLocalPart)) {
mySubState = ResourceReferenceSubState.DISPLAY;
break;
} else if ("reference".equals(theLocalPart)) {
mySubState = ResourceReferenceSubState.REFERENCE;
break;
} else if ("resource".equals(theLocalPart)) {
mySubState = ResourceReferenceSubState.REFERENCE;
break;
} else {
logAndSwallowUnexpectedElement(theLocalPart);
break;
}
case DISPLAY:
case REFERENCE:
logAndSwallowUnexpectedElement(theLocalPart);
break;
}
}
@Override
protected IBaseReference getCurrentElement() {
return myInstance;
}
}
private enum ResourceReferenceSubState {
DISPLAY, INITIAL, REFERENCE
}
private class ResourceStateHapi extends ElementCompositeState {
private IResource myInstance;

View File

@ -46,5 +46,29 @@ public class StrictErrorHandler implements IParserErrorHandler {
throw new DataFormatException("Multiple repetitions of non-repeatable element '" + theElementName + "' found during parse");
}
@Override
public void containedResourceWithNoId(IParseLocation theLocation) {
throw new DataFormatException("Resource has contained child resource with no ID");
}
@Override
public void unknownReference(IParseLocation theLocation, String theReference) {
throw new DataFormatException("Resource has invalid reference: " + theReference);
}
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
StringBuilder b = new StringBuilder();
b.append("Resource is missing required element '");
b.append(theElementName);
b.append("'");
if (theLocation != null) {
b.append(" in parent element '");
b.append(theLocation.getParentElementName());
b.append("'");
}
throw new DataFormatException(b.toString());
}
}

View File

@ -45,6 +45,7 @@ import javax.xml.stream.events.Namespace;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import ca.uhn.fhir.model.api.BaseBundle;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
@ -174,7 +175,7 @@ public class XmlParser extends BaseParser implements IParser {
try {
eventWriter = createXmlWriter(theWriter);
encodeResourceToXmlStreamWriter(theResource, eventWriter, false);
encodeResourceToXmlStreamWriter(theResource, eventWriter, false, false);
eventWriter.flush();
} catch (XMLStreamException e) {
throw new ConfigurationException("Failed to initialize STaX event factory", e);
@ -205,16 +206,24 @@ public class XmlParser extends BaseParser implements IParser {
if ("extension".equals(elem.getName().getLocalPart())) {
Attribute urlAttr = elem.getAttributeByName(new QName("url"));
String url;
if (urlAttr == null || isBlank(urlAttr.getValue())) {
throw new DataFormatException("Extension element has no 'url' attribute");
getErrorHandler().missingRequiredElement(new ParseLocation("extension"), "url");
url = null;
} else {
url = urlAttr.getValue();
}
parserState.enteringNewElementExtension(elem, urlAttr.getValue(), false);
parserState.enteringNewElementExtension(elem, url, false);
} else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
Attribute urlAttr = elem.getAttributeByName(new QName("url"));
String url;
if (urlAttr == null || isBlank(urlAttr.getValue())) {
throw new DataFormatException("Extension element has no 'url' attribute");
getErrorHandler().missingRequiredElement(new ParseLocation("modifierExtension"), "url");
url = null;
} else {
url = urlAttr.getValue();
}
parserState.enteringNewElementExtension(elem, urlAttr.getValue(), true);
parserState.enteringNewElementExtension(elem, url, true);
} else {
String elementName = elem.getName().getLocalPart();
parserState.enteringNewElement(namespaceURI, elementName);
@ -236,12 +245,6 @@ public class XmlParser extends BaseParser implements IParser {
break;
}
case XMLStreamConstants.ATTRIBUTE: {
Attribute elem = (Attribute) nextEvent;
String name = (elem.getName().getLocalPart());
parserState.attributeValue(name, elem.getValue());
break;
}
case XMLStreamConstants.END_DOCUMENT:
case XMLStreamConstants.END_ELEMENT: {
if (!heldComments.isEmpty()) {
@ -315,12 +318,7 @@ public class XmlParser extends BaseParser implements IParser {
writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());
if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
eventWriter.writeStartElement("author");
writeTagWithTextNode(eventWriter, "name", theBundle.getAuthorName());
writeOptionalTagWithTextNode(eventWriter, "uri", theBundle.getAuthorUri());
eventWriter.writeEndElement();
}
writeAuthor(eventWriter, theBundle);
writeCategories(eventWriter, theBundle.getCategories());
@ -372,6 +370,8 @@ public class XmlParser extends BaseParser implements IParser {
writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
writeAuthor(eventWriter, nextEntry);
writeCategories(eventWriter, nextEntry.getCategories());
if (!nextEntry.getLinkSelf().isEmpty()) {
@ -390,7 +390,7 @@ public class XmlParser extends BaseParser implements IParser {
if (resource != null && !resource.isEmpty() && !deleted) {
eventWriter.writeStartElement("content");
eventWriter.writeAttribute("type", "text/xml");
encodeResourceToXmlStreamWriter(resource, eventWriter, false);
encodeResourceToXmlStreamWriter(resource, eventWriter, false, true);
eventWriter.writeEndElement(); // content
} else {
ourLog.debug("Bundle entry contains null resource");
@ -453,7 +453,7 @@ public class XmlParser extends BaseParser implements IParser {
IResource resource = nextEntry.getResource();
if (resource != null && !resource.isEmpty() && !deleted) {
theEventWriter.writeStartElement("resource");
encodeResourceToXmlStreamWriter(resource, theEventWriter, false);
encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
theEventWriter.writeEndElement(); // content
} else {
ourLog.debug("Bundle entry contains null resource");
@ -531,7 +531,6 @@ public class XmlParser extends BaseParser implements IParser {
}
break;
}
case RESOURCE_REF:
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
theEventWriter.writeStartElement(childName);
@ -564,7 +563,7 @@ public class XmlParser extends BaseParser implements IParser {
case RESOURCE: {
theEventWriter.writeStartElement(childName);
IBaseResource resource = (IBaseResource) theElement;
encodeResourceToXmlStreamWriter(resource, theEventWriter, false);
encodeResourceToXmlStreamWriter(resource, theEventWriter, false, true);
theEventWriter.writeEndElement();
break;
}
@ -597,7 +596,6 @@ public class XmlParser extends BaseParser implements IParser {
}
@SuppressWarnings("rawtypes")
private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, boolean theContainedResource, CompositeChildElement theParent)
throws XMLStreamException, DataFormatException {
@ -640,7 +638,7 @@ public class XmlParser extends BaseParser implements IParser {
} else {
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
values = super.preProcessValues(nextChild, theResource, values);
values = super.preProcessValues(nextChild, theResource, values, nextChildElem);
if (values == null || values.isEmpty()) {
continue;
@ -650,32 +648,15 @@ public class XmlParser extends BaseParser implements IParser {
continue;
}
Class<? extends IBase> type = nextValue.getClass();
String childName = nextChild.getChildNameByDatatype(type);
String extensionUrl = nextChild.getExtensionUrl();
BaseRuntimeElementDefinition<?> childDef = nextChild.getChildElementDefinitionByDatatype(type);
if (childDef == null) {
if (nextValue instanceof IBaseExtension) {
continue;
}
/*
* For RI structures Enumeration class, this replaces the child def
* with the "code" one. This is messy, and presumably there is a better
* way..
*/
BaseRuntimeElementDefinition<?> elementDef = myContext.getElementDefinition(type);
if (elementDef.getName().equals("code")) {
Class type2 = myContext.getElementDefinition("code").getImplementingClass();
childDef = nextChild.getChildElementDefinitionByDatatype(type2);
childName = nextChild.getChildNameByDatatype(type2);
}
if (childDef == null) {
super.throwExceptionForUnknownChildType(nextChild, type);
}
BaseParser.ChildNameAndDef childNameAndDef = super.getChildNameAndDef(nextChild, nextValue);
if (childNameAndDef == null) {
continue;
}
String childName = childNameAndDef.getChildName();
BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
String extensionUrl = nextChild.getExtensionUrl();
if (nextValue instanceof IBaseExtension && myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
// This is called for the Query resource in DSTU1 only
extensionUrl = ((IBaseExtension<?, ?>) nextValue).getUrl();
@ -736,7 +717,7 @@ public class XmlParser extends BaseParser implements IParser {
}
}
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException {
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource, boolean theSubResource) throws XMLStreamException, DataFormatException {
IIdType resourceId = null;
if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) {
@ -752,7 +733,7 @@ public class XmlParser extends BaseParser implements IParser {
if (!theIncludedResource) {
if (super.shouldEncodeResourceId(theResource) == false) {
resourceId = null;
} else if (getEncodeForceResourceId() != null) {
} else if (theSubResource == false && getEncodeForceResourceId() != null) {
resourceId = getEncodeForceResourceId();
}
}
@ -1095,6 +1076,15 @@ public class XmlParser extends BaseParser implements IParser {
return retVal;
}
private void writeAuthor(XMLStreamWriter theEventWriter, BaseBundle theBundle) throws XMLStreamException {
if (StringUtils.isNotBlank(theBundle.getAuthorName().getValue())) {
theEventWriter.writeStartElement("author");
writeTagWithTextNode(theEventWriter, "name", theBundle.getAuthorName());
writeOptionalTagWithTextNode(theEventWriter, "uri", theBundle.getAuthorUri());
theEventWriter.writeEndElement();
}
}
private void writeAtomLink(XMLStreamWriter theEventWriter, String theRel, StringDt theStringDt) throws XMLStreamException {
if (StringUtils.isNotBlank(theStringDt.getValue())) {
theEventWriter.writeStartElement("link");

View File

@ -0,0 +1,389 @@
package ca.uhn.fhir.parser.json;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.PushbackReader;
import java.io.Reader;
import java.io.Writer;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import ca.uhn.fhir.parser.DataFormatException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class GsonStructure implements JsonLikeStructure {
private enum ROOT_TYPE {OBJECT, ARRAY};
private ROOT_TYPE rootType = null;
private JsonElement nativeRoot = null;
private JsonLikeValue jsonLikeRoot = null;
private GsonWriter jsonLikeWriter = null;
public GsonStructure() {
super();
}
public GsonStructure (JsonObject json) {
super();
setNativeObject(json);
}
public GsonStructure (JsonArray json) {
super();
setNativeArray(json);
}
public void setNativeObject (JsonObject json) {
this.rootType = ROOT_TYPE.OBJECT;
this.nativeRoot = json;
}
public void setNativeArray (JsonArray json) {
this.rootType = ROOT_TYPE.ARRAY;
this.nativeRoot = json;
}
@Override
public JsonLikeStructure getInstance() {
return new GsonStructure();
}
@Override
public void load(Reader theReader) throws DataFormatException {
this.load(theReader, false);
}
@Override
public void load(Reader theReader, boolean allowArray) throws DataFormatException {
PushbackReader pbr = new PushbackReader(theReader);
int nextInt;
try {
while(true) {
nextInt = pbr.read();
if (nextInt == -1) {
throw new DataFormatException("Did not find any content to parse");
}
if (nextInt == '{') {
pbr.unread(nextInt);
break;
}
if (Character.isWhitespace(nextInt)) {
continue;
}
if (allowArray) {
if (nextInt == '[') {
pbr.unread(nextInt);
break;
}
throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{' or '[')");
}
throw new DataFormatException("Content does not appear to be FHIR JSON, first non-whitespace character was: '" + (char)nextInt + "' (must be '{')");
}
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
if (nextInt == '{') {
JsonObject root = gson.fromJson(pbr, JsonObject.class);
setNativeObject(root);
} else
if (nextInt == '[') {
JsonArray root = gson.fromJson(pbr, JsonArray.class);
setNativeArray(root);
}
} catch (JsonSyntaxException e) {
if (e.getMessage().startsWith("Unexpected char 39")) {
throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e);
}
throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e);
} catch (Exception e) {
throw new DataFormatException("Failed to parse JSON content, error was: " + e.getMessage(), e);
}
}
@Override
public JsonLikeWriter getJsonLikeWriter (Writer writer) {
if (null == jsonLikeWriter) {
jsonLikeWriter = new GsonWriter(writer);
}
return jsonLikeWriter;
}
@Override
public JsonLikeWriter getJsonLikeWriter () {
if (null == jsonLikeWriter) {
jsonLikeWriter = new GsonWriter();
}
return jsonLikeWriter;
}
@Override
public JsonLikeObject getRootObject() throws DataFormatException {
if (rootType == ROOT_TYPE.OBJECT) {
if (null == jsonLikeRoot) {
jsonLikeRoot = new GsonJsonObject((JsonObject)nativeRoot);
}
return jsonLikeRoot.getAsObject();
}
throw new DataFormatException("Content must be a valid JSON Object. It must start with '{'.");
}
@Override
public JsonLikeArray getRootArray() throws DataFormatException {
if (rootType == ROOT_TYPE.ARRAY) {
if (null == jsonLikeRoot) {
jsonLikeRoot = new GsonJsonArray((JsonArray)nativeRoot);
}
return jsonLikeRoot.getAsArray();
}
throw new DataFormatException("Content must be a valid JSON Array. It must start with '['.");
}
private static class GsonJsonObject extends JsonLikeObject {
private JsonObject nativeObject;
private Set<String> keySet = null;
private Map<String,JsonLikeValue> jsonLikeMap = new LinkedHashMap<String,JsonLikeValue>();
public GsonJsonObject (JsonObject json) {
this.nativeObject = json;
}
@Override
public Object getValue() {
return null;
}
@Override
public Set<String> keySet() {
if (null == keySet) {
Set<Entry<String, JsonElement>> entrySet = nativeObject.entrySet();
keySet = new EntryOrderedSet<String>(entrySet.size());
for (Entry<String,?> entry : entrySet) {
keySet.add(entry.getKey());
}
}
return keySet;
}
@Override
public JsonLikeValue get(String key) {
JsonLikeValue result = null;
if (jsonLikeMap.containsKey(key)) {
result = jsonLikeMap.get(key);
} else {
JsonElement child = nativeObject.get(key);
if (child != null) {
result = new GsonJsonValue(child);
}
jsonLikeMap.put(key, result);
}
return result;
}
}
private static class GsonJsonArray extends JsonLikeArray {
private JsonArray nativeArray;
private Map<Integer,JsonLikeValue> jsonLikeMap = new LinkedHashMap<Integer,JsonLikeValue>();
public GsonJsonArray (JsonArray json) {
this.nativeArray = json;
}
@Override
public Object getValue() {
return null;
}
@Override
public int size() {
return nativeArray.size();
}
@Override
public JsonLikeValue get(int index) {
Integer key = Integer.valueOf(index);
JsonLikeValue result = null;
if (jsonLikeMap.containsKey(key)) {
result = jsonLikeMap.get(key);
} else {
JsonElement child = nativeArray.get(index);
if (child != null) {
result = new GsonJsonValue(child);
}
jsonLikeMap.put(key, result);
}
return result;
}
}
private static class GsonJsonValue extends JsonLikeValue {
private JsonElement nativeValue;
private JsonLikeObject jsonLikeObject = null;
private JsonLikeArray jsonLikeArray = null;
public GsonJsonValue (JsonElement json) {
this.nativeValue = json;
}
@Override
public Object getValue() {
if (nativeValue != null && nativeValue.isJsonPrimitive()) {
if (((JsonPrimitive)nativeValue).isNumber()) {
return nativeValue.getAsNumber();
}
if (((JsonPrimitive)nativeValue).isBoolean()) {
return Boolean.valueOf(nativeValue.getAsBoolean());
}
return nativeValue.getAsString();
}
return null;
}
@Override
public ValueType getJsonType() {
if (null == nativeValue || nativeValue.isJsonNull()) {
return ValueType.NULL;
}
if (nativeValue.isJsonObject()) {
return ValueType.OBJECT;
}
if (nativeValue.isJsonArray()) {
return ValueType.ARRAY;
}
if (nativeValue.isJsonPrimitive()) {
return ValueType.SCALAR;
}
return null;
}
@Override
public ScalarType getDataType() {
if (nativeValue != null && nativeValue.isJsonPrimitive()) {
if (((JsonPrimitive)nativeValue).isNumber()) {
return ScalarType.NUMBER;
}
if (((JsonPrimitive)nativeValue).isString()) {
return ScalarType.STRING;
}
if (((JsonPrimitive)nativeValue).isBoolean()) {
return ScalarType.BOOLEAN;
}
}
return null;
}
@Override
public JsonLikeArray getAsArray() {
if (nativeValue != null && nativeValue.isJsonArray()) {
if (null == jsonLikeArray) {
jsonLikeArray = new GsonJsonArray((JsonArray)nativeValue);
}
}
return jsonLikeArray;
}
@Override
public JsonLikeObject getAsObject() {
if (nativeValue != null && nativeValue.isJsonObject()) {
if (null == jsonLikeObject) {
jsonLikeObject = new GsonJsonObject((JsonObject)nativeValue);
}
}
return jsonLikeObject;
}
@Override
public Number getAsNumber() {
return nativeValue != null ? nativeValue.getAsNumber() : null;
}
@Override
public String getAsString() {
return nativeValue != null ? nativeValue.getAsString() : null;
}
@Override
public boolean getAsBoolean() {
if (nativeValue != null && nativeValue.isJsonPrimitive() && ((JsonPrimitive)nativeValue).isBoolean()) {
return nativeValue.getAsBoolean();
}
return super.getAsBoolean();
}
}
private static class EntryOrderedSet<T> extends AbstractSet<T> {
private transient ArrayList<T> data = null;
public EntryOrderedSet (int initialCapacity) {
data = new ArrayList<T>(initialCapacity);
}
@SuppressWarnings("unused")
public EntryOrderedSet () {
data = new ArrayList<T>();
}
@Override
public int size() {
return data.size();
}
@Override
public boolean contains(Object o) {
return data.contains(o);
}
@SuppressWarnings("unused") // not really.. just not here
public T get(int index) {
return data.get(index);
}
@Override
public boolean add(T element) {
if (data.contains(element)) {
return false;
}
return data.add(element);
}
@Override
public boolean remove(Object o) {
return data.remove(o);
}
@Override
public void clear() {
data.clear();
}
@Override
public Iterator<T> iterator() {
return data.iterator();
}
}
}

View File

@ -0,0 +1,263 @@
package ca.uhn.fhir.parser.json;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Stack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.stream.JsonWriter;
public class GsonWriter extends JsonLikeWriter {
private static final Logger log = LoggerFactory.getLogger(GsonWriter.class);
private JsonWriter eventWriter;
private enum BlockType {
NONE, OBJECT, ARRAY
}
private BlockType blockType = BlockType.NONE;
private Stack<BlockType> blockStack = new Stack<BlockType>();
public GsonWriter () {
super();
}
public GsonWriter (Writer writer) {
setWriter(writer);
}
@Override
public JsonLikeWriter init() throws IOException {
eventWriter = new JsonWriter(getWriter());
eventWriter.setSerializeNulls(true);
if (isPrettyPrint()) {
eventWriter.setIndent(" ");
}
blockType = BlockType.NONE;
blockStack.clear();
return this;
}
@Override
public JsonLikeWriter flush() throws IOException {
if (blockType != BlockType.NONE) {
log.error("JsonLikeStreamWriter.flush() called but JSON document is not finished");
}
eventWriter.flush();
getWriter().flush();
return this;
}
@Override
public void close() throws IOException {
eventWriter.close();
getWriter().close();
}
@Override
public JsonLikeWriter beginObject() throws IOException {
blockStack.push(blockType);
blockType = BlockType.OBJECT;
eventWriter.beginObject();
return this;
}
@Override
public JsonLikeWriter beginArray() throws IOException {
blockStack.push(blockType);
blockType = BlockType.ARRAY;
eventWriter.beginArray();
return this;
}
@Override
public JsonLikeWriter beginObject(String name) throws IOException {
blockStack.push(blockType);
blockType = BlockType.OBJECT;
eventWriter.name(name);
eventWriter.beginObject();
return this;
}
@Override
public JsonLikeWriter beginArray(String name) throws IOException {
blockStack.push(blockType);
blockType = BlockType.ARRAY;
eventWriter.name(name);
eventWriter.beginArray();
return this;
}
@Override
public JsonLikeWriter write(String value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(BigInteger value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(BigDecimal value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(long value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(double value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(Boolean value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(boolean value) throws IOException {
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter writeNull() throws IOException {
eventWriter.nullValue();
return this;
}
@Override
public JsonLikeWriter write(String name, String value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, BigInteger value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, BigDecimal value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, long value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, double value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, Boolean value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter write(String name, boolean value) throws IOException {
eventWriter.name(name);
eventWriter.value(value);
return this;
}
@Override
public JsonLikeWriter writeNull(String name) throws IOException {
eventWriter.name(name);
eventWriter.nullValue();
return this;
}
@Override
public JsonLikeWriter endObject() throws IOException {
if (blockType == BlockType.NONE) {
log.error("JsonLikeStreamWriter.endObject(); called with no active JSON document");
} else {
if (blockType != BlockType.OBJECT) {
log.error("JsonLikeStreamWriter.endObject(); called outside a JSON object. (Use endArray() instead?)");
eventWriter.endArray();
} else {
eventWriter.endObject();
}
blockType = blockStack.pop();
}
return this;
}
@Override
public JsonLikeWriter endArray() throws IOException {
if (blockType == BlockType.NONE) {
log.error("JsonLikeStreamWriter.endArray(); called with no active JSON document");
} else {
if (blockType != BlockType.ARRAY) {
log.error("JsonLikeStreamWriter.endArray(); called outside a JSON array. (Use endObject() instead?)");
eventWriter.endObject();
} else {
eventWriter.endArray();
}
blockType = blockStack.pop();
}
return this;
}
@Override
public JsonLikeWriter endBlock() throws IOException {
if (blockType == BlockType.NONE) {
log.error("JsonLikeStreamWriter.endBlock(); called with no active JSON document");
} else {
if (blockType == BlockType.ARRAY) {
eventWriter.endArray();
} else {
eventWriter.endObject();
}
blockType = blockStack.pop();
}
return this;
}
}

View File

@ -0,0 +1,53 @@
package ca.uhn.fhir.parser.json;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public abstract class JsonLikeArray extends JsonLikeValue {
@Override
public ValueType getJsonType() {
return ValueType.ARRAY;
}
@Override
public ScalarType getDataType() {
return null;
}
@Override
public boolean isArray() {
return true;
}
@Override
public JsonLikeArray getAsArray() {
return this;
}
@Override
public String getAsString() {
return null;
}
public abstract int size ();
public abstract JsonLikeValue get (int index);
}

View File

@ -0,0 +1,72 @@
package ca.uhn.fhir.parser.json;
import java.util.Set;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public abstract class JsonLikeObject extends JsonLikeValue {
@Override
public ValueType getJsonType() {
return ValueType.OBJECT;
}
@Override
public ScalarType getDataType() {
return null;
}
@Override
public boolean isObject() {
return true;
}
@Override
public JsonLikeObject getAsObject() {
return this;
}
@Override
public String getAsString() {
return null;
}
public abstract Set<String> keySet ();
public abstract JsonLikeValue get (String key);
public String getString (String key) {
JsonLikeValue value = this.get(key);
if (null == value) {
throw new NullPointerException("Json object missing element named \""+key+"\"");
}
return value.getAsString();
}
public String getString (String key, String defaultValue) {
String result = defaultValue;
JsonLikeValue value = this.get(key);
if (value != null) {
result = value.getAsString();
}
return result;
}
}

View File

@ -0,0 +1,52 @@
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.parser.json;
import java.io.Reader;
import java.io.Writer;
import ca.uhn.fhir.parser.DataFormatException;
/**
* This interface is the generic representation of any sort of data
* structure that looks and smells like JSON. These data structures
* can be abstractly viewed as a <code.Map</code> or <code>List</code>
* whose members are other Maps, Lists, or scalars (Strings, Numbers, Boolean)
*
* @author Bill.Denton
*/
public interface JsonLikeStructure {
public JsonLikeStructure getInstance();
/**
* Parse the JSON document into the Json-like structure
* so that it can be navigated.
*
* @param theReader a <code>Reader</code> that will
* process the JSON input stream
* @throws DataFormatException when invalid JSON is received
*/
public void load (Reader theReader) throws DataFormatException;
public void load (Reader theReader, boolean allowArray) throws DataFormatException;
public JsonLikeObject getRootObject () throws DataFormatException;
public JsonLikeArray getRootArray () throws DataFormatException;
public JsonLikeWriter getJsonLikeWriter ();
public JsonLikeWriter getJsonLikeWriter (Writer writer);
}

View File

@ -0,0 +1,228 @@
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.parser.json;
/**
* This is the generalization of anything that is a "value"
* element in a JSON structure. This could be a JSON object,
* a JSON array, a scalar value (number, string, boolean),
* or a null.
*
*/
public abstract class JsonLikeValue {
public enum ValueType {
ARRAY, OBJECT, SCALAR, NULL
};
public enum ScalarType {
NUMBER, STRING, BOOLEAN
}
public abstract ValueType getJsonType ();
public abstract ScalarType getDataType ();
public abstract Object getValue ();
public boolean isArray () {
return this.getJsonType() == ValueType.ARRAY;
}
public boolean isObject () {
return this.getJsonType() == ValueType.OBJECT;
}
public boolean isScalar () {
return this.getJsonType() == ValueType.SCALAR;
}
public boolean isString () {
return this.getJsonType() == ValueType.SCALAR && this.getDataType() == ScalarType.STRING;
}
public boolean isNumber () {
return this.getJsonType() == ValueType.SCALAR && this.getDataType() == ScalarType.NUMBER;
}
public boolean isNull () {
return this.getJsonType() == ValueType.NULL;
}
public JsonLikeArray getAsArray () {
return null;
}
public JsonLikeObject getAsObject () {
return null;
}
public String getAsString () {
return this.toString();
}
public Number getAsNumber () {
return this.isNumber() ? (Number)this.getValue() : null;
}
public boolean getAsBoolean () {
return !isNull();
}
public static JsonLikeArray asArray (JsonLikeValue element) {
if (element != null) {
return element.getAsArray();
}
return null;
}
public static JsonLikeObject asObject (JsonLikeValue element) {
if (element != null) {
return element.getAsObject();
}
return null;
}
public static String asString (JsonLikeValue element) {
if (element != null) {
return element.getAsString();
}
return null;
}
public static boolean asBoolean (JsonLikeValue element) {
if (element != null) {
return element.getAsBoolean();
}
return false;
}
public static final JsonLikeValue NULL = new JsonLikeValue() {
@Override
public ValueType getJsonType() {
return ValueType.NULL;
}
@Override
public ScalarType getDataType() {
return null;
}
@Override
public Object getValue() {
return null;
}
@Override
public boolean equals (Object obj) {
if (this == obj){
return true;
}
if (obj instanceof JsonLikeValue) {
return getJsonType().equals(((JsonLikeValue)obj).getJsonType());
}
return false;
}
@Override
public int hashCode() {
return "null".hashCode();
}
@Override
public String toString() {
return "null";
}
};
public static final JsonLikeValue TRUE = new JsonLikeValue() {
@Override
public ValueType getJsonType() {
return ValueType.SCALAR;
}
@Override
public ScalarType getDataType() {
return ScalarType.BOOLEAN;
}
@Override
public Object getValue() {
return Boolean.TRUE;
}
@Override
public boolean equals(Object obj) {
if (this == obj){
return true;
}
if (obj instanceof JsonLikeValue) {
return getJsonType().equals(((JsonLikeValue)obj).getJsonType())
&& getDataType().equals(((JsonLikeValue)obj).getDataType())
&& toString().equals(((JsonLikeValue)obj).toString());
}
return false;
}
@Override
public int hashCode() {
return "true".hashCode();
}
@Override
public String toString() {
return "true";
}
};
public static final JsonLikeValue FALSE = new JsonLikeValue() {
@Override
public ValueType getJsonType() {
return ValueType.SCALAR;
}
@Override
public ScalarType getDataType() {
return ScalarType.BOOLEAN;
}
@Override
public Object getValue() {
return Boolean.FALSE;
}
@Override
public boolean equals(Object obj) {
if (this == obj){
return true;
}
if (obj instanceof JsonLikeValue) {
return getJsonType().equals(((JsonLikeValue)obj).getJsonType())
&& getDataType().equals(((JsonLikeValue)obj).getDataType())
&& toString().equals(((JsonLikeValue)obj).toString());
}
return false;
}
@Override
public int hashCode() {
return "false".hashCode();
}
@Override
public String toString() {
return "false";
}
};
}

View File

@ -0,0 +1,83 @@
package ca.uhn.fhir.parser.json;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public abstract class JsonLikeWriter {
private boolean prettyPrint;
private Writer writer;
public void setPrettyPrint (boolean tf) {
prettyPrint = tf;
}
public boolean isPrettyPrint () {
return prettyPrint;
}
public void setWriter (Writer writer) {
this.writer = writer;
}
public Writer getWriter () {
return writer;
}
public abstract JsonLikeWriter init () throws IOException;
public abstract JsonLikeWriter flush () throws IOException;
public abstract void close () throws IOException;
public abstract JsonLikeWriter beginObject () throws IOException;
public abstract JsonLikeWriter beginArray () throws IOException;
public abstract JsonLikeWriter beginObject (String name) throws IOException;
public abstract JsonLikeWriter beginArray (String name) throws IOException;
public abstract JsonLikeWriter write (String value) throws IOException;
public abstract JsonLikeWriter write (BigInteger value) throws IOException;
public abstract JsonLikeWriter write (BigDecimal value) throws IOException;
public abstract JsonLikeWriter write (long value) throws IOException;
public abstract JsonLikeWriter write (double value) throws IOException;
public abstract JsonLikeWriter write (Boolean value) throws IOException;
public abstract JsonLikeWriter write (boolean value) throws IOException;
public abstract JsonLikeWriter writeNull () throws IOException;
public abstract JsonLikeWriter write (String name, String value) throws IOException;
public abstract JsonLikeWriter write (String name, BigInteger value) throws IOException;
public abstract JsonLikeWriter write (String name, BigDecimal value) throws IOException;
public abstract JsonLikeWriter write (String name, long value) throws IOException;
public abstract JsonLikeWriter write (String name, double value) throws IOException;
public abstract JsonLikeWriter write (String name, Boolean value) throws IOException;
public abstract JsonLikeWriter write (String name, boolean value) throws IOException;
public abstract JsonLikeWriter writeNull (String name) throws IOException;
public abstract JsonLikeWriter endObject () throws IOException;
public abstract JsonLikeWriter endArray () throws IOException;
public abstract JsonLikeWriter endBlock () throws IOException;
public JsonLikeWriter() {
super();
}
}

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