diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 6cce600e631..e5554769f37 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.util.ReflectionUtil; import org.hl7.fhir.instance.model.api.*; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; @@ -44,56 +45,47 @@ import static org.apache.commons.lang3.StringUtils.isBlank; class ModelScanner { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class); - private Map, BaseRuntimeElementDefinition> myClassToElementDefinitions = new HashMap, BaseRuntimeElementDefinition>(); + private Map, BaseRuntimeElementDefinition> myClassToElementDefinitions = new HashMap<>(); private FhirContext myContext; - private Map myIdToResourceDefinition = new HashMap(); - private Map> myNameToElementDefinitions = new HashMap>(); - private Map myNameToResourceDefinitions = new HashMap(); - private Map> myNameToResourceType = new HashMap>(); + private Map myIdToResourceDefinition = new HashMap<>(); + private Map> myNameToElementDefinitions = new HashMap<>(); + private Map myNameToResourceDefinitions = new HashMap<>(); + private Map> myNameToResourceType = new HashMap<>(); private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; - private Set> myScanAlso = new HashSet>(); + private Set> myScanAlso = new HashSet<>(); private FhirVersionEnum myVersion; private Set> myVersionTypes; ModelScanner(FhirContext theContext, FhirVersionEnum theVersion, Map, BaseRuntimeElementDefinition> theExistingDefinitions, - Collection> theResourceTypes) throws ConfigurationException { + @Nonnull Collection> theResourceTypes) throws ConfigurationException { myContext = theContext; myVersion = theVersion; - Set> toScan; - if (theResourceTypes != null) { - toScan = new HashSet>(theResourceTypes); - } else { - toScan = new HashSet>(); - } + Set> toScan = new HashSet<>(theResourceTypes); init(theExistingDefinitions, toScan); } - public Map, BaseRuntimeElementDefinition> getClassToElementDefinitions() { + Map, BaseRuntimeElementDefinition> getClassToElementDefinitions() { return myClassToElementDefinitions; } - public Map getIdToResourceDefinition() { + Map getIdToResourceDefinition() { return myIdToResourceDefinition; } - public Map> getNameToElementDefinitions() { + Map> getNameToElementDefinitions() { return myNameToElementDefinitions; } - public Map getNameToResourceDefinition() { + Map getNameToResourceDefinition() { return myNameToResourceDefinitions; } - public Map getNameToResourceDefinitions() { - return (myNameToResourceDefinitions); - } - - public Map> getNameToResourceType() { + Map> getNameToResourceType() { return myNameToResourceType; } - public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { + RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() { return myRuntimeChildUndeclaredExtensionDefinition; } @@ -145,11 +137,10 @@ class ModelScanner { } private boolean isStandardType(Class theClass) { - boolean retVal = myVersionTypes.contains(theClass); - return retVal; + return myVersionTypes.contains(theClass); } - private void scan(Class theClass) throws ConfigurationException { + void scan(Class theClass) throws ConfigurationException { BaseRuntimeElementDefinition existingDef = myClassToElementDefinitions.get(theClass); if (existingDef != null) { return; @@ -204,9 +195,6 @@ class ModelScanner { ourLog.debug("Scanning resource block class: {}", theClass.getName()); String resourceName = theClass.getCanonicalName(); - if (isBlank(resourceName)) { - 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)) { @@ -341,14 +329,14 @@ class ModelScanner { private void scanResourceForSearchParams(Class theClass, RuntimeResourceDefinition theResourceDef) { - Map nameToParam = new HashMap(); - Map compositeFields = new LinkedHashMap(); + Map nameToParam = new HashMap<>(); + Map compositeFields = new LinkedHashMap<>(); /* * Make sure we pick up fields in interfaces too.. This ensures that we * grab the _id field which generally gets picked up via interface */ - Set fields = new HashSet(Arrays.asList(theClass.getFields())); + Set fields = new HashSet<>(Arrays.asList(theClass.getFields())); Class nextClass = theClass; do { for (Class nextInterface : nextClass.getInterfaces()) { @@ -400,12 +388,12 @@ class ModelScanner { for (Entry nextEntry : compositeFields.entrySet()) { SearchParamDefinition searchParam = nextEntry.getValue(); - List compositeOf = new ArrayList(); + List compositeOf = new ArrayList<>(); for (String nextName : searchParam.compositeOf()) { RuntimeSearchParam param = nameToParam.get(nextName); if (param == null) { ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}", - new Object[]{theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()}); + theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()); continue; } compositeOf.add(param); @@ -417,7 +405,7 @@ class ModelScanner { } private Set toTargetList(Class[] theTarget) { - HashSet retVal = new HashSet(); + HashSet retVal = new HashSet<>(); for (Class nextType : theTarget) { ResourceDef resourceDef = nextType.getAnnotation(ResourceDef.class); @@ -486,7 +474,7 @@ class ModelScanner { } static Set> scanVersionPropertyFile(Set> theDatatypes, Map> theResourceTypes, FhirVersionEnum theVersion, Map, BaseRuntimeElementDefinition> theExistingElementDefinitions) { - Set> retVal = new HashSet>(); + Set> retVal = new HashSet<>(); try (InputStream str = theVersion.getVersionImplementation().getFhirVersionPropertiesFile()) { Properties prop = new Properties(); diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java index 198ef7b9b8d..c365df459c3 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java @@ -1,17 +1,19 @@ package ca.uhn.fhir.context; -import ca.uhn.fhir.model.api.annotation.*; import ca.uhn.fhir.model.api.annotation.Extension; +import ca.uhn.fhir.model.api.annotation.*; 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.hl7.fhir.instance.model.api.IBase; import org.junit.AfterClass; import org.junit.Ignore; import org.junit.Test; -import java.util.List; +import java.util.*; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.*; public class ModelScannerDstu3Test { @@ -149,6 +151,73 @@ public class ModelScannerDstu3Test { } } + @Test + public void testScanDuplicate() { + FhirContext ctx = FhirContext.forDstu3(); + FhirVersionEnum version = FhirVersionEnum.DSTU3; + Map, BaseRuntimeElementDefinition> definitions = new HashMap<>(); + Collection> resourceTypes = new ArrayList<>(); + resourceTypes.add(Patient.class); + ModelScanner scanner = new ModelScanner(ctx, version, definitions, resourceTypes); + assertThat(resourceTypes, contains(Patient.class)); + + // Extra scans don't do anything + scanner.scan(Patient.class); + scanner.scan(Patient.class); + assertThat(resourceTypes, contains(Patient.class)); + } + + @Test + public void testScanInvalidResource() { + FhirContext ctx = FhirContext.forDstu3(); + FhirVersionEnum version = FhirVersionEnum.DSTU3; + Map, BaseRuntimeElementDefinition> definitions = new HashMap<>(); + Collection> resourceTypes = new ArrayList<>(); + ModelScanner scanner = new ModelScanner(ctx, version, definitions, resourceTypes); + + try { + scanner.scan(BadPatient.class); + fail(); + } catch (ConfigurationException e) { + assertEquals("Resource type contains a @ResourceDef annotation but does not implement ca.uhn.fhir.model.api.IResource: ca.uhn.fhir.context.ModelScannerDstu3Test.BadPatient", e.getMessage()); + } + } + + @Test + public void testScanInvalidType() { + FhirContext ctx = FhirContext.forDstu3(); + FhirVersionEnum version = FhirVersionEnum.DSTU3; + Map, BaseRuntimeElementDefinition> definitions = new HashMap<>(); + Collection> resourceTypes = new ArrayList<>(); + ModelScanner scanner = new ModelScanner(ctx, version, definitions, resourceTypes); + + Class clazz = String.class; + try { + scanner.scan(clazz); + fail(); + } catch (ConfigurationException e) { + assertEquals("Resource class[java.lang.String] does not contain any valid HAPI-FHIR annotations", e.getMessage()); + } + } + + @Test + public void testScanInvalidBlock() { + + + FhirContext ctx = FhirContext.forDstu3(); + FhirVersionEnum version = FhirVersionEnum.DSTU3; + Map, BaseRuntimeElementDefinition> definitions = new HashMap<>(); + Collection> resourceTypes = new ArrayList<>(); + ModelScanner scanner = new ModelScanner(ctx, version, definitions, resourceTypes); + + try { + scanner.scan(BadPatient.BadBlock.class); + fail(); + } catch (ConfigurationException e) { + assertEquals("Type contains a @Block annotation but does not implement ca.uhn.fhir.model.api.IResourceBlock: ca.uhn.fhir.context.ModelScannerDstu3Test.BadPatient.BadBlock", e.getMessage()); + } + } + class NoResourceDef extends Patient { @SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "bar") public static final String SP_TELECOM = "foo"; @@ -156,6 +225,78 @@ public class ModelScannerDstu3Test { } + @ResourceDef(name = "Patient") + public static class BadPatient implements IBase { + + @Child(name = "badBlock") + private BadBlock myChild; + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean hasFormatComment() { + return false; + } + + @Override + public List getFormatCommentsPre() { + return null; + } + + @Override + public List getFormatCommentsPost() { + return null; + } + + @Override + public Object getUserData(String theName) { + return null; + } + + @Override + public void setUserData(String theName, Object theValue) { + + } + + @Block + public static class BadBlock implements IBase { + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean hasFormatComment() { + return false; + } + + @Override + public List getFormatCommentsPre() { + return null; + } + + @Override + public List getFormatCommentsPost() { + return null; + } + + @Override + public Object getUserData(String theName) { + return null; + } + + @Override + public void setUserData(String theName, Object theValue) { + + } + } + + } + @ResourceDef(name = "Patient") public static class CompartmentForNonReferenceParam extends Patient { @SearchParamDefinition(name = "foo", path = "Patient.telecom", type = "string", providesMembershipIn = {@Compartment(name = "Patient"), @Compartment(name = "Device")})