Add a warning if an invalid class is scanned

This commit is contained in:
James Agnew 2018-11-16 10:42:21 +01:00
parent 14c0a52831
commit e4f6b3e9a2
6 changed files with 437 additions and 193 deletions

View File

@ -115,7 +115,7 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
}
} while (current != null);
Set<Field> fields = new HashSet<Field>();
Set<Field> fields = new HashSet<>();
for (Class<? extends IBase> nextClass : classes) {
int fieldIndexInClass = 0;
for (Field next : nextClass.getDeclaredFields()) {
@ -181,11 +181,17 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IBase> ext
if (IBase.class.isAssignableFrom(next.getElementType())) {
if (next.getElementType().isInterface() == false && Modifier.isAbstract(next.getElementType().getModifiers()) == false) {
theScanAlso.add((Class<? extends IBase>) next.getElementType());
if (theScanAlso.toString().contains("ExtensionDt")) {
theScanAlso.toString();//FIXME: remove
}
}
}
for (Class<? extends IBase> nextChildType : next.getChoiceTypes()) {
if (nextChildType.isInterface() == false && Modifier.isAbstract(nextChildType.getModifiers()) == false) {
theScanAlso.add(nextChildType);
if (theScanAlso.toString().contains("ExtensionDt")) {
theScanAlso.toString();//FIXME: remove
}
}
}
}

View File

@ -113,11 +113,7 @@ class ModelScanner {
for (Class<? extends IBase> nextClass : typesToScan) {
scan(nextClass);
}
for (Iterator<Class<? extends IBase>> iter = myScanAlso.iterator(); iter.hasNext(); ) {
if (myClassToElementDefinitions.containsKey(iter.next())) {
iter.remove();
}
}
myScanAlso.removeIf(theClass -> myClassToElementDefinitions.containsKey(theClass));
typesToScan.clear();
typesToScan.addAll(myScanAlso);
myScanAlso.clear();
@ -212,6 +208,13 @@ class ModelScanner {
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
}
// Just in case someone messes up when upgrading from DSTU2
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
if (BaseIdentifiableElement.class.isAssignableFrom(theClass)) {
throw new ConfigurationException("@Block class for version " + myContext.getVersion().getVersion().name() + " should not extend " + BaseIdentifiableElement.class.getSimpleName() + ": " + theClass.getName());
}
}
RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions);
blockDef.populateScanAlso(myScanAlso);

View File

@ -149,9 +149,9 @@ public class RuntimeChildUndeclaredExtensionDefinition extends BaseRuntimeChildD
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
Map<String, BaseRuntimeElementDefinition<?>> datatypeAttributeNameToDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>();
myDatatypeToAttributeName = new HashMap<Class<? extends IBase>, String>();
myDatatypeToDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
Map<String, BaseRuntimeElementDefinition<?>> datatypeAttributeNameToDefinition = new HashMap<>();
myDatatypeToAttributeName = new HashMap<>();
myDatatypeToDefinition = new HashMap<>();
for (BaseRuntimeElementDefinition<?> next : theClassToElementDefinitions.values()) {
if (next instanceof IRuntimeDatatypeDefinition) {

View File

@ -0,0 +1,238 @@
package ca.uhn.fhir.context;
import ca.uhn.fhir.model.api.BaseIdentifiableElement;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IExtension;
import ca.uhn.fhir.model.api.annotation.*;
import ca.uhn.fhir.model.api.annotation.Extension;
import org.hl7.fhir.dstu3.model.*;
import java.util.List;
@ResourceDef(name = "ResourceWithExtensionsA", id="0001")
public class CustomDstu3ClassWithDstu2Base extends DomainResource {
/*
* NB: several unit tests depend on the structure here
* so check the unit tests immediately after any changes
*/
private static final long serialVersionUID = 1L;
@Child(name = "foo1", type = StringType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://foo/#f1", definedLocally=true, isModifier=false)
private List<StringType> myFoo1;
@Child(name = "foo2", type = StringType.class, order = 1, min = 0, max = 1)
@Extension(url = "http://foo/#f2", definedLocally=true, isModifier=true)
private StringType myFoo2;
@Child(name = "bar1", type = Bar1.class, order = 2, min = 1, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1", definedLocally=true, isModifier=false)
private List<Bar1> myBar1;
@Child(name = "bar2", type = Bar1.class, order = 3, min = 1, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b2", definedLocally=true, isModifier=false)
private Bar1 myBar2;
@Child(name="baz", type = CodeableConcept.class, order = 4)
@Extension(url= "http://baz/#baz", definedLocally=true, isModifier=false)
@Description(shortDefinition = "Contains a codeable concept")
private CodeableConcept myBaz;
@Child(name = "identifier", type = Identifier.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
private List<Identifier> myIdentifier;
public List<Bar1> getBar1() {
return myBar1;
}
public Bar1 getBar2() {
return myBar2;
}
public List<StringType> getFoo1() {
return myFoo1;
}
public StringType getFoo2() {
return myFoo2;
}
public CodeableConcept getBaz() { return myBaz; }
public List<Identifier> getIdentifier() {
return myIdentifier;
}
public void setBar1(List<Bar1> theBar1) {
myBar1 = theBar1;
}
public void setBar2(Bar1 theBar2) {
myBar2 = theBar2;
}
public void setFoo1(List<StringType> theFoo1) {
myFoo1 = theFoo1;
}
public void setFoo2(StringType theFoo2) {
myFoo2 = theFoo2;
}
public void setBaz(CodeableConcept myBaz) { this.myBaz = myBaz; }
public void setIdentifier(List<Identifier> theValue) {
myIdentifier = theValue;
}
@Block(name = "Bar1")
public static class Bar1 extends BaseIdentifiableElement implements IExtension {
public Bar1() {
super();
}
@Child(name = "bar11", type = DateType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/1", definedLocally=true, isModifier=false)
private List<DateType> myBar11;
@Child(name = "bar12", type = DateType.class, order = 1, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2", definedLocally=true, isModifier=false)
private List<Bar2> myBar12;
private IdType myId;
@Override
public boolean isEmpty() {
return false; // not implemented
}
@Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
}
public List<DateType> getBar11() {
return myBar11;
}
public List<Bar2> getBar12() {
return myBar12;
}
public void setBar11(List<DateType> theBar11) {
myBar11 = theBar11;
}
public void setBar12(List<Bar2> theBar12) {
myBar12 = theBar12;
}
}
@Block(name = "Bar2")
public static class Bar2 extends BaseIdentifiableElement implements IExtension {
@Child(name = "bar121", type = DateType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2/1", definedLocally=true, isModifier=false)
private List<DateType> myBar121;
@Child(name = "bar122", type = DateType.class, order = 1, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2/2", definedLocally=true, isModifier=false)
private List<DateType> myBar122;
@Override
public boolean isEmpty() {
return false; // not implemented
}
@Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
}
public List<DateType> getBar121() {
return myBar121;
}
public List<DateType> getBar122() {
return myBar122;
}
public void setBar121(List<DateType> theBar121) {
myBar121 = theBar121;
}
public void setBar122(List<DateType> theBar122) {
myBar122 = theBar122;
}
}
@Override
public boolean isEmpty() {
return false; // not implemented
}
@Override
public String getId() {
return null;
}
@Override
public IdType getIdElement() {
return null;
}
@Override
public CodeType getLanguageElement() {
return null;
}
@Override
public Resource setId(String theId) {
return null;
}
@Override
public Meta getMeta() {
return null;
}
@Override
public Resource setIdElement(IdType theIdType) {
return null;
}
@Override
public String fhirType() {
return null;
}
@Override
protected void listChildren(List<Property> theResult) {
// nothing
}
@Override
public DomainResource copy() {
return null;
}
@Override
public ResourceType getResourceType() {
return null;
}
}

View File

@ -1,29 +1,21 @@
package ca.uhn.fhir.context;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.util.List;
import org.hl7.fhir.dstu3.model.*;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import ca.uhn.fhir.model.api.annotation.*;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.*;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.*;
public class ModelScannerDstu3Test {
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testScanBundle() {
FhirContext ctx = FhirContext.forDstu3();
@ -44,7 +36,9 @@ public class ModelScannerDstu3Test {
}
}
/** This failed at one point */
/**
* This failed at one point
*/
@Test
public void testCarePlan() throws DataFormatException {
FhirContext.forDstu3().getResourceDefinition(CarePlan.class);
@ -106,6 +100,17 @@ public class ModelScannerDstu3Test {
}
@Test
public void testScanDstu3TypeWithDstu2Backend() throws DataFormatException {
FhirContext ctx = FhirContext.forDstu3();
try {
ctx.getResourceDefinition(CustomDstu3ClassWithDstu2Base.class);
fail();
} catch (ConfigurationException e) {
assertEquals("@Block class for version DSTU3 should not extend BaseIdentifiableElement: ca.uhn.fhir.context.CustomDstu3ClassWithDstu2Base$Bar1", e.getMessage());
}
}
/**
* TODO: Re-enable this when Claim's compartment defs are cleaned up
*/
@ -130,21 +135,40 @@ public class ModelScannerDstu3Test {
}
}
@ResourceDef(name = "Patient")
public static class CompartmentForNonReferenceParam extends Patient {
/**
* See #504
*/
@Test
public void testBinaryMayNotHaveExtensions() {
FhirContext ctx = FhirContext.forDstu3();
try {
ctx.getResourceDefinition(LetterTemplate.class);
fail();
} catch (ConfigurationException e) {
assertEquals("Class \"class ca.uhn.fhir.context.ModelScannerDstu3Test$LetterTemplate\" is invalid. This resource type is not a DomainResource, it must not have extensions", e.getMessage());
}
}
class NoResourceDef extends Patient {
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "bar")
public static final String SP_TELECOM = "foo";
private static final long serialVersionUID = 1L;
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "string", providesMembershipIn = { @Compartment(name = "Patient"), @Compartment(name = "Device") })
}
@ResourceDef(name = "Patient")
public static class CompartmentForNonReferenceParam extends Patient {
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "string", providesMembershipIn = {@Compartment(name = "Patient"), @Compartment(name = "Device")})
public static final String SP_TELECOM = "foo";
private static final long serialVersionUID = 1L;
}
@ResourceDef(name = "Patient")
public static class InvalidParamType extends Patient {
private static final long serialVersionUID = 1L;
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "bar")
public static final String SP_TELECOM = "foo";
private static final long serialVersionUID = 1L;
}
@ -204,33 +228,11 @@ public class ModelScannerDstu3Test {
}
class NoResourceDef extends Patient {
private static final long serialVersionUID = 1L;
@SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "bar")
public static final String SP_TELECOM = "foo";
}
/**
* See #504
*/
@Test
public void testBinaryMayNotHaveExtensions() {
FhirContext ctx = FhirContext.forDstu3();
try {
ctx.getResourceDefinition(LetterTemplate.class);
fail();
} catch (ConfigurationException e) {
assertEquals("Class \"class ca.uhn.fhir.context.ModelScannerDstu3Test$LetterTemplate\" is invalid. This resource type is not a DomainResource, it must not have extensions", e.getMessage());
}
}
@ResourceDef(name = "Binary", id = "letter-template", profile = "http://www.something.org/StructureDefinition/letter-template")
public static class LetterTemplate extends Binary {
private static final long serialVersionUID = 1L;
@Child(name = "name")
@Extension(url = "http://example.com/dontuse#name", definedLocally = false, isModifier = false)
@Description(shortDefinition = "The name of the template")
@ -239,13 +241,18 @@ public class ModelScannerDstu3Test {
public LetterTemplate() {
}
public void setName(StringDt name) {
myName = name;
}
public StringDt getName() {
return myName;
}
public void setName(StringDt name) {
myName = name;
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -1,47 +1,40 @@
package ca.uhn.fhir.context;
import ca.uhn.fhir.model.api.annotation.*;
import ca.uhn.fhir.model.api.annotation.Extension;
import org.hl7.fhir.dstu3.model.*;
import java.util.List;
import org.hl7.fhir.dstu3.model.*;
import ca.uhn.fhir.model.api.BaseIdentifiableElement;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IExtension;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
@ResourceDef(name = "ResourceWithExtensionsA", id="0001")
@ResourceDef(name = "ResourceWithExtensionsA", id = "0001")
public class ResourceWithExtensionsDstu3A extends DomainResource {
/*
* NB: several unit tests depend on the structure here
* so check the unit tests immediately after any changes
* so check the unit tests immediately after any changes
*/
private static final long serialVersionUID = 1L;
@Child(name = "foo1", type = StringType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://foo/#f1", definedLocally=true, isModifier=false)
@Extension(url = "http://foo/#f1", definedLocally = true, isModifier = false)
private List<StringType> myFoo1;
@Child(name = "foo2", type = StringType.class, order = 1, min = 0, max = 1)
@Extension(url = "http://foo/#f2", definedLocally=true, isModifier=true)
@Extension(url = "http://foo/#f2", definedLocally = true, isModifier = true)
private StringType myFoo2;
@Child(name = "bar1", type = Bar1.class, order = 2, min = 1, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1", definedLocally=true, isModifier=false)
@Extension(url = "http://bar/#b1", definedLocally = true, isModifier = false)
private List<Bar1> myBar1;
@Child(name = "bar2", type = Bar1.class, order = 3, min = 1, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b2", definedLocally=true, isModifier=false)
@Extension(url = "http://bar/#b2", definedLocally = true, isModifier = false)
private Bar1 myBar2;
@Child(name="baz", type = CodeableConcept.class, order = 4)
@Extension(url= "http://baz/#baz", definedLocally=true, isModifier=false)
@Description(shortDefinition = "Contains a codeable concept")
@Child(name = "baz", type = CodeableConcept.class, order = 4)
@Extension(url = "http://baz/#baz", definedLocally = true, isModifier = false)
@Description(shortDefinition = "Contains a codeable concept")
private CodeableConcept myBaz;
@Child(name = "identifier", type = Identifier.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@ -51,143 +44,55 @@ public class ResourceWithExtensionsDstu3A extends DomainResource {
return myBar1;
}
public Bar1 getBar2() {
return myBar2;
}
public List<StringType> getFoo1() {
return myFoo1;
}
public StringType getFoo2() {
return myFoo2;
}
public CodeableConcept getBaz() { return myBaz; }
public List<Identifier> getIdentifier() {
return myIdentifier;
}
public void setBar1(List<Bar1> theBar1) {
myBar1 = theBar1;
}
public Bar1 getBar2() {
return myBar2;
}
public void setBar2(Bar1 theBar2) {
myBar2 = theBar2;
}
public List<StringType> getFoo1() {
return myFoo1;
}
public void setFoo1(List<StringType> theFoo1) {
myFoo1 = theFoo1;
}
public StringType getFoo2() {
return myFoo2;
}
public void setFoo2(StringType theFoo2) {
myFoo2 = theFoo2;
}
public void setBaz(CodeableConcept myBaz) { this.myBaz = myBaz; }
public CodeableConcept getBaz() {
return myBaz;
}
public void setBaz(CodeableConcept myBaz) {
this.myBaz = myBaz;
}
public List<Identifier> getIdentifier() {
return myIdentifier;
}
public void setIdentifier(List<Identifier> theValue) {
myIdentifier = theValue;
}
@Block(name = "Bar1")
public static class Bar1 extends BaseIdentifiableElement implements IExtension {
public Bar1() {
super();
}
@Child(name = "bar11", type = DateType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/1", definedLocally=true, isModifier=false)
private List<DateType> myBar11;
@Child(name = "bar12", type = DateType.class, order = 1, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2", definedLocally=true, isModifier=false)
private List<Bar2> myBar12;
private IdType myId;
@Override
public boolean isEmpty() {
return false; // not implemented
}
@Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
}
public List<DateType> getBar11() {
return myBar11;
}
public List<Bar2> getBar12() {
return myBar12;
}
public void setBar11(List<DateType> theBar11) {
myBar11 = theBar11;
}
public void setBar12(List<Bar2> theBar12) {
myBar12 = theBar12;
}
}
@Block(name = "Bar2")
public static class Bar2 extends BaseIdentifiableElement implements IExtension {
@Child(name = "bar121", type = DateType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2/1", definedLocally=true, isModifier=false)
private List<DateType> myBar121;
@Child(name = "bar122", type = DateType.class, order = 1, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2/2", definedLocally=true, isModifier=false)
private List<DateType> myBar122;
@Override
public boolean isEmpty() {
return false; // not implemented
}
@Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
}
public List<DateType> getBar121() {
return myBar121;
}
public List<DateType> getBar122() {
return myBar122;
}
public void setBar121(List<DateType> theBar121) {
myBar121 = theBar121;
}
public void setBar122(List<DateType> theBar122) {
myBar122 = theBar122;
}
}
@Override
public boolean isEmpty() {
return false; // not implemented
}
@Override
public String getId() {
return null;
@ -238,5 +143,90 @@ public class ResourceWithExtensionsDstu3A extends DomainResource {
return null;
}
@Block(name = "Bar1")
public static class Bar1 extends BackboneElement {
}
@Child(name = "bar11", type = DateType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/1", definedLocally = true, isModifier = false)
private List<DateType> myBar11;
@Child(name = "bar12", type = DateType.class, order = 1, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2", definedLocally = true, isModifier = false)
private List<Bar2> myBar12;
private IdType myId;
public Bar1() {
super();
}
@Override
public BackboneElement copy() {
return this;
}
@Override
public boolean isEmpty() {
return false; // not implemented
}
public List<DateType> getBar11() {
return myBar11;
}
public void setBar11(List<DateType> theBar11) {
myBar11 = theBar11;
}
public List<Bar2> getBar12() {
return myBar12;
}
public void setBar12(List<Bar2> theBar12) {
myBar12 = theBar12;
}
}
@Block(name = "Bar2")
public static class Bar2 extends BackboneElement {
@Child(name = "bar121", type = DateType.class, order = 0, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2/1", definedLocally = true, isModifier = false)
private List<DateType> myBar121;
@Child(name = "bar122", type = DateType.class, order = 1, min = 0, max = Child.MAX_UNLIMITED)
@Extension(url = "http://bar/#b1/2/2", definedLocally = true, isModifier = false)
private List<DateType> myBar122;
@Override
public BackboneElement copy() {
return this;
}
@Override
public boolean isEmpty() {
return false; // not implemented
}
public List<DateType> getBar121() {
return myBar121;
}
public void setBar121(List<DateType> theBar121) {
myBar121 = theBar121;
}
public List<DateType> getBar122() {
return myBar122;
}
public void setBar122(List<DateType> theBar122) {
myBar122 = theBar122;
}
}
}