2014-02-17 17:58:32 -05:00
package ca.uhn.fhir.context ;
2014-04-24 12:25:47 -04:00
/ *
* # % L
2014-07-21 09:24:38 -04:00
* HAPI FHIR - Core Library
2014-04-24 12:25:47 -04:00
* % %
2017-01-09 17:48:42 -05:00
* Copyright ( C ) 2014 - 2017 University Health Network
2014-04-24 12:25:47 -04:00
* % %
* 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 %
* /
2015-08-20 15:27:24 -04:00
import static org.apache.commons.lang3.StringUtils.isBlank ;
2014-02-17 17:58:32 -05:00
2014-05-07 08:18:43 -04:00
import java.io.IOException ;
import java.io.InputStream ;
2014-12-10 17:40:47 -05:00
import java.lang.annotation.Annotation ;
import java.lang.reflect.AnnotatedElement ;
2014-02-17 17:58:32 -05:00
import java.lang.reflect.Field ;
2014-02-19 13:02:51 -05:00
import java.lang.reflect.ParameterizedType ;
2014-03-03 09:05:55 -05:00
import java.lang.reflect.Type ;
2017-01-13 21:19:19 -06:00
import java.util.* ;
2014-07-02 08:57:07 -04:00
import java.util.Map.Entry ;
2014-02-17 17:58:32 -05:00
2016-06-16 07:42:06 -04:00
import org.apache.commons.io.IOUtils ;
2017-01-13 21:46:16 -06:00
import org.hl7.fhir.instance.model.api.* ;
2014-12-10 14:28:25 -05:00
2017-02-01 05:55:35 -05:00
import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum ;
2014-05-28 16:07:53 -04:00
import ca.uhn.fhir.model.api.ExtensionDt ;
2014-02-17 17:58:32 -05:00
import ca.uhn.fhir.model.api.IDatatype ;
import ca.uhn.fhir.model.api.IElement ;
import ca.uhn.fhir.model.api.IResource ;
2014-02-20 12:13:05 -05:00
import ca.uhn.fhir.model.api.IResourceBlock ;
2014-03-03 09:05:55 -05:00
import ca.uhn.fhir.model.api.IValueSetEnumBinder ;
2014-02-20 12:13:05 -05:00
import ca.uhn.fhir.model.api.annotation.Block ;
2014-02-18 08:26:49 -05:00
import ca.uhn.fhir.model.api.annotation.Child ;
2016-02-26 18:16:35 -05:00
import ca.uhn.fhir.model.api.annotation.Compartment ;
2014-02-19 11:59:12 -05:00
import ca.uhn.fhir.model.api.annotation.DatatypeDef ;
2014-02-18 18:10:50 -05:00
import ca.uhn.fhir.model.api.annotation.ResourceDef ;
2014-05-05 05:57:43 -07:00
import ca.uhn.fhir.model.api.annotation.SearchParamDefinition ;
2014-03-03 09:05:55 -05:00
import ca.uhn.fhir.model.primitive.BoundCodeDt ;
2014-02-24 11:46:08 -05:00
import ca.uhn.fhir.model.primitive.XhtmlDt ;
2015-03-24 18:39:19 -04:00
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum ;
2014-03-16 15:09:30 -04:00
import ca.uhn.fhir.util.ReflectionUtil ;
2014-02-17 17:58:32 -05:00
class ModelScanner {
private static final org . slf4j . Logger ourLog = org . slf4j . LoggerFactory . getLogger ( ModelScanner . class ) ;
2014-12-10 14:28:25 -05:00
private Map < Class < ? extends IBase > , BaseRuntimeElementDefinition < ? > > myClassToElementDefinitions = new HashMap < Class < ? extends IBase > , BaseRuntimeElementDefinition < ? > > ( ) ;
2015-02-14 13:27:11 -05:00
private FhirContext myContext ;
2014-03-11 08:39:55 -04:00
private Map < String , RuntimeResourceDefinition > myIdToResourceDefinition = new HashMap < String , RuntimeResourceDefinition > ( ) ;
2015-05-31 16:52:31 -04:00
private Map < String , BaseRuntimeElementDefinition < ? > > myNameToElementDefinitions = new HashMap < String , BaseRuntimeElementDefinition < ? > > ( ) ;
2014-02-18 18:10:50 -05:00
private Map < String , RuntimeResourceDefinition > myNameToResourceDefinitions = new HashMap < String , RuntimeResourceDefinition > ( ) ;
2015-02-14 13:27:11 -05:00
private Map < String , Class < ? extends IBaseResource > > myNameToResourceType = new HashMap < String , Class < ? extends IBaseResource > > ( ) ;
2014-02-22 15:33:02 -05:00
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition ;
2014-12-10 14:28:25 -05:00
private Set < Class < ? extends IBase > > myScanAlso = new HashSet < Class < ? extends IBase > > ( ) ;
2015-02-14 13:27:11 -05:00
private FhirVersionEnum myVersion ;
2015-05-31 16:52:31 -04:00
2015-05-02 14:04:43 -07:00
private Set < Class < ? extends IBase > > myVersionTypes ;
2014-03-11 08:39:55 -04:00
2016-02-26 18:16:35 -05:00
ModelScanner ( FhirContext theContext , FhirVersionEnum theVersion , Map < Class < ? extends IBase > , BaseRuntimeElementDefinition < ? > > theExistingDefinitions ,
2016-05-21 14:35:10 -04:00
Collection < Class < ? extends IBase > > theResourceTypes ) throws ConfigurationException {
2014-12-01 08:13:32 -05:00
myContext = theContext ;
2015-02-14 13:27:11 -05:00
myVersion = theVersion ;
2014-12-11 16:07:00 -05:00
Set < Class < ? extends IBase > > toScan ;
2014-12-01 08:13:32 -05:00
if ( theResourceTypes ! = null ) {
2014-12-11 16:07:00 -05:00
toScan = new HashSet < Class < ? extends IBase > > ( theResourceTypes ) ;
2014-12-01 08:13:32 -05:00
} else {
2014-12-11 16:07:00 -05:00
toScan = new HashSet < Class < ? extends IBase > > ( ) ;
2014-12-01 08:13:32 -05:00
}
init ( theExistingDefinitions , toScan ) ;
2014-03-04 16:41:18 -05:00
}
2014-02-18 18:10:50 -05:00
2016-06-06 11:15:11 -07:00
static Class < ? > determineElementType ( Field next ) {
2014-03-11 08:39:55 -04:00
Class < ? > nextElementType = next . getType ( ) ;
if ( List . class . equals ( nextElementType ) ) {
2014-03-16 15:09:30 -04:00
nextElementType = ReflectionUtil . getGenericCollectionTypeOfField ( next ) ;
2014-03-11 08:39:55 -04:00
} else if ( Collection . class . isAssignableFrom ( nextElementType ) ) {
throw new ConfigurationException ( " Field ' " + next . getName ( ) + " ' in type ' " + next . getClass ( ) . getCanonicalName ( ) + " ' is a Collection - Only java.util.List curently supported " ) ;
}
return nextElementType ;
}
@SuppressWarnings ( " unchecked " )
2016-06-06 11:15:11 -07:00
static IValueSetEnumBinder < Enum < ? > > getBoundCodeBinder ( Field theNext ) {
2014-03-11 08:39:55 -04:00
Class < ? > bound = getGenericCollectionTypeOfCodedField ( theNext ) ;
if ( bound = = null ) {
throw new ConfigurationException ( " Field ' " + theNext + " ' has no parameter for " + BoundCodeDt . class . getSimpleName ( ) + " to determine enum type " ) ;
}
2016-07-03 10:23:48 -04:00
String fieldName = " VALUESET_BINDER " ;
2014-03-11 08:39:55 -04:00
try {
2016-07-03 10:23:48 -04:00
Field bindingField = bound . getField ( fieldName ) ;
2014-03-11 08:39:55 -04:00
return ( IValueSetEnumBinder < Enum < ? > > ) bindingField . get ( null ) ;
2016-07-03 10:23:48 -04:00
} 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 ) ;
2014-03-11 08:39:55 -04:00
}
}
2015-02-14 13:27:11 -05:00
public Map < Class < ? extends IBase > , BaseRuntimeElementDefinition < ? > > getClassToElementDefinitions ( ) {
return myClassToElementDefinitions ;
}
public Map < String , RuntimeResourceDefinition > getIdToResourceDefinition ( ) {
return myIdToResourceDefinition ;
}
2015-05-31 16:52:31 -04:00
public Map < String , BaseRuntimeElementDefinition < ? > > getNameToElementDefinitions ( ) {
return myNameToElementDefinitions ;
}
public Map < String , RuntimeResourceDefinition > getNameToResourceDefinition ( ) {
return myNameToResourceDefinitions ;
}
2015-02-14 13:27:11 -05:00
public Map < String , RuntimeResourceDefinition > getNameToResourceDefinitions ( ) {
return ( myNameToResourceDefinitions ) ;
}
public Map < String , Class < ? extends IBaseResource > > getNameToResourceType ( ) {
return myNameToResourceType ;
}
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition ( ) {
return myRuntimeChildUndeclaredExtensionDefinition ;
}
2016-08-14 13:05:16 -04:00
private void init ( Map < Class < ? extends IBase > , BaseRuntimeElementDefinition < ? > > theExistingDefinitions , Set < Class < ? extends IBase > > theTypesToScan ) {
2014-07-04 09:27:36 -04:00
if ( theExistingDefinitions ! = null ) {
2014-07-02 08:57:07 -04:00
myClassToElementDefinitions . putAll ( theExistingDefinitions ) ;
}
2014-07-04 09:27:36 -04:00
2014-07-02 08:57:07 -04:00
int startSize = myClassToElementDefinitions . size ( ) ;
2014-05-07 08:18:43 -04:00
long start = System . currentTimeMillis ( ) ;
2015-02-14 13:27:11 -05:00
Map < String , Class < ? extends IBaseResource > > resourceTypes = myNameToResourceType ;
2014-05-07 08:18:43 -04:00
2016-08-14 13:05:16 -04:00
Set < Class < ? extends IBase > > typesToScan = theTypesToScan ;
myVersionTypes = scanVersionPropertyFile ( typesToScan , resourceTypes , myVersion , myClassToElementDefinitions ) ;
2014-05-07 08:18:43 -04:00
2014-02-18 18:10:50 -05:00
do {
2016-08-14 13:05:16 -04:00
for ( Class < ? extends IBase > nextClass : typesToScan ) {
2014-02-18 18:10:50 -05:00
scan ( nextClass ) ;
}
2014-12-10 14:28:25 -05:00
for ( Iterator < Class < ? extends IBase > > iter = myScanAlso . iterator ( ) ; iter . hasNext ( ) ; ) {
2014-02-18 18:10:50 -05:00
if ( myClassToElementDefinitions . containsKey ( iter . next ( ) ) ) {
iter . remove ( ) ;
}
}
2016-08-14 13:05:16 -04:00
typesToScan . clear ( ) ;
typesToScan . addAll ( myScanAlso ) ;
2014-02-18 18:10:50 -05:00
myScanAlso . clear ( ) ;
2016-08-14 13:05:16 -04:00
} while ( ! typesToScan . isEmpty ( ) ) ;
2014-02-19 11:59:12 -05:00
2014-12-10 14:28:25 -05:00
for ( Entry < Class < ? extends IBase > , BaseRuntimeElementDefinition < ? > > nextEntry : myClassToElementDefinitions . entrySet ( ) ) {
2014-07-04 09:27:36 -04:00
if ( theExistingDefinitions ! = null & & theExistingDefinitions . containsKey ( nextEntry . getKey ( ) ) ) {
2014-07-02 08:57:07 -04:00
continue ;
}
BaseRuntimeElementDefinition < ? > next = nextEntry . getValue ( ) ;
2016-06-06 11:15:11 -07:00
boolean deferredSeal = false ;
if ( myContext . getPerformanceOptions ( ) . contains ( PerformanceOptionsEnum . DEFERRED_MODEL_SCANNING ) ) {
if ( next instanceof BaseRuntimeElementCompositeDefinition ) {
deferredSeal = true ;
}
}
if ( ! deferredSeal ) {
next . sealAndInitialize ( myContext , myClassToElementDefinitions ) ;
}
2014-02-17 17:58:32 -05:00
}
2014-02-19 11:59:12 -05:00
2014-02-23 18:13:59 -05:00
myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition ( ) ;
2015-02-12 17:32:00 -05:00
myRuntimeChildUndeclaredExtensionDefinition . sealAndInitialize ( myContext , myClassToElementDefinitions ) ;
2014-03-03 09:05:55 -05:00
2014-05-07 08:18:43 -04:00
long time = System . currentTimeMillis ( ) - start ;
2014-07-04 09:27:36 -04:00
int size = myClassToElementDefinitions . size ( ) - startSize ;
2015-02-23 09:08:59 -05:00
ourLog . debug ( " Done scanning FHIR library, found {} model entries in {}ms " , size , time ) ;
2014-02-17 17:58:32 -05:00
}
2015-05-31 16:52:31 -04:00
private boolean isStandardType ( Class < ? extends IBase > theClass ) {
2015-12-30 11:27:12 -06:00
boolean retVal = myVersionTypes . contains ( theClass ) ;
return retVal ;
2015-05-31 16:52:31 -04:00
}
2015-02-14 13:27:11 -05:00
/ * *
2016-02-26 18:16:35 -05:00
* There are two implementations of all of the annotations ( e . g . { @link Child } and { @link org . hl7 . fhir . instance . model . annotations . Child } ) since the HL7 . org ones will eventually replace the HAPI
* 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 .
2015-02-14 13:27:11 -05:00
* /
2016-06-06 11:15:11 -07:00
static < T extends Annotation > T pullAnnotation ( AnnotatedElement theTarget , Class < T > theAnnotationType ) {
2015-02-14 13:27:11 -05:00
T retVal = theTarget . getAnnotation ( theAnnotationType ) ;
2016-02-26 18:16:35 -05:00
return retVal ;
2015-02-14 13:27:11 -05:00
}
2014-12-10 14:28:25 -05:00
private void scan ( Class < ? extends IBase > theClass ) throws ConfigurationException {
2014-02-18 18:10:50 -05:00
BaseRuntimeElementDefinition < ? > existingDef = myClassToElementDefinitions . get ( theClass ) ;
2014-02-17 17:58:32 -05:00
if ( existingDef ! = null ) {
2014-02-20 12:13:05 -05:00
return ;
2014-02-17 17:58:32 -05:00
}
2016-02-26 18:16:35 -05:00
ResourceDef resourceDefinition = pullAnnotation ( theClass , ResourceDef . class ) ;
2014-02-17 17:58:32 -05:00
if ( resourceDefinition ! = null ) {
2015-02-16 11:33:46 -05:00
if ( ! IBaseResource . class . isAssignableFrom ( theClass ) ) {
2016-02-26 18:16:35 -05:00
throw new ConfigurationException (
" Resource type contains a @ " + ResourceDef . class . getSimpleName ( ) + " annotation but does not implement " + IResource . class . getCanonicalName ( ) + " : " + theClass . getCanonicalName ( ) ) ;
2014-02-17 17:58:32 -05:00
}
@SuppressWarnings ( " unchecked " )
2014-12-10 17:40:47 -05:00
Class < ? extends IBaseResource > resClass = ( Class < ? extends IBaseResource > ) theClass ;
2014-02-20 12:13:05 -05:00
scanResource ( resClass , resourceDefinition ) ;
2016-08-14 13:05:16 -04:00
return ;
2014-02-17 17:58:32 -05:00
}
2016-02-26 18:16:35 -05:00
DatatypeDef datatypeDefinition = pullAnnotation ( theClass , DatatypeDef . class ) ;
2014-02-17 17:58:32 -05:00
if ( datatypeDefinition ! = null ) {
2014-12-10 14:28:25 -05:00
if ( ICompositeType . class . isAssignableFrom ( theClass ) ) {
2014-02-17 17:58:32 -05:00
@SuppressWarnings ( " unchecked " )
2014-12-10 14:28:25 -05:00
Class < ? extends ICompositeType > resClass = ( Class < ? extends ICompositeType > ) theClass ;
2014-02-20 12:13:05 -05:00
scanCompositeDatatype ( resClass , datatypeDefinition ) ;
2014-12-10 14:28:25 -05:00
} else if ( IPrimitiveType . class . isAssignableFrom ( theClass ) ) {
2014-02-20 12:13:05 -05:00
@SuppressWarnings ( { " unchecked " } )
2014-12-14 22:29:15 -05:00
Class < ? extends IPrimitiveType < ? > > resClass = ( Class < ? extends IPrimitiveType < ? > > ) theClass ;
2014-02-20 12:13:05 -05:00
scanPrimitiveDatatype ( resClass , datatypeDefinition ) ;
2016-08-14 13:05:16 -04:00
}
return ;
2014-02-17 17:58:32 -05:00
}
2016-02-26 18:16:35 -05:00
Block blockDefinition = pullAnnotation ( theClass , Block . class ) ;
2015-02-16 11:33:46 -05:00
2014-02-20 12:13:05 -05:00
if ( blockDefinition ! = null ) {
2015-05-05 13:16:10 -04:00
if ( IResourceBlock . class . isAssignableFrom ( theClass ) | | IBaseBackboneElement . class . isAssignableFrom ( theClass ) | | IBaseDatatypeElement . class . isAssignableFrom ( theClass ) ) {
2015-02-16 11:33:46 -05:00
scanBlock ( theClass ) ;
2014-02-21 21:06:11 -05:00
} else {
2016-02-26 18:16:35 -05:00
throw new ConfigurationException (
" Type contains a @ " + Block . class . getSimpleName ( ) + " annotation but does not implement " + IResourceBlock . class . getCanonicalName ( ) + " : " + theClass . getCanonicalName ( ) ) ;
2014-02-20 12:13:05 -05:00
}
}
2014-08-07 08:27:52 -04:00
if ( blockDefinition = = null & & datatypeDefinition = = null & & resourceDefinition = = null ) {
2014-12-04 11:04:53 -05:00
throw new ConfigurationException ( " Resource class[ " + theClass . getName ( ) + " ] does not contain any valid HAPI-FHIR annotations " ) ;
2014-02-20 12:13:05 -05:00
}
}
2015-02-16 11:33:46 -05:00
private void scanBlock ( Class < ? extends IBase > theClass ) {
2014-02-20 12:13:05 -05:00
ourLog . debug ( " Scanning resource block class: {} " , theClass . getName ( ) ) ;
2014-02-18 18:10:50 -05:00
2014-08-07 08:27:52 -04:00
String resourceName = theClass . getCanonicalName ( ) ;
2014-02-20 12:13:05 -05:00
if ( isBlank ( resourceName ) ) {
throw new ConfigurationException ( " Block type @ " + Block . class . getSimpleName ( ) + " annotation contains no name: " + theClass . getCanonicalName ( ) ) ;
}
2016-06-06 11:15:11 -07:00
RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition ( resourceName , theClass , isStandardType ( theClass ) , myContext , myClassToElementDefinitions ) ;
myClassToElementDefinitions . put ( theClass , blockDef ) ;
2014-02-17 17:58:32 -05:00
}
2014-12-10 17:40:47 -05:00
private void scanCompositeDatatype ( Class < ? extends ICompositeType > theClass , DatatypeDef theDatatypeDefinition ) {
2015-02-18 17:00:47 -05:00
ourLog . debug ( " Scanning datatype class: {} " , theClass . getName ( ) ) ;
2014-02-17 17:58:32 -05:00
2016-06-06 11:15:11 -07:00
RuntimeCompositeDatatypeDefinition elementDef ;
2014-05-28 16:07:53 -04:00
if ( theClass . equals ( ExtensionDt . class ) ) {
2016-06-06 11:15:11 -07:00
elementDef = new RuntimeExtensionDtDefinition ( theDatatypeDefinition , theClass , true , myContext , myClassToElementDefinitions ) ;
2016-02-26 18:16:35 -05:00
// } else if (IBaseMetaType.class.isAssignableFrom(theClass)) {
// resourceDef = new RuntimeMetaDefinition(theDatatypeDefinition, theClass, isStandardType(theClass));
2014-05-28 16:07:53 -04:00
} else {
2016-06-06 11:15:11 -07:00
elementDef = new RuntimeCompositeDatatypeDefinition ( theDatatypeDefinition , theClass , isStandardType ( theClass ) , myContext , myClassToElementDefinitions ) ;
2014-05-28 16:07:53 -04:00
}
2016-06-06 11:15:11 -07:00
myClassToElementDefinitions . put ( theClass , elementDef ) ;
myNameToElementDefinitions . put ( elementDef . getName ( ) . toLowerCase ( ) , elementDef ) ;
2016-08-14 13:05:16 -04:00
/ *
* 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
* /
elementDef . populateScanAlso ( myScanAlso ) ;
2014-02-17 17:58:32 -05:00
}
2014-02-19 17:33:46 -05:00
2014-08-15 15:55:39 -04:00
2016-06-06 11:15:11 -07:00
static Class < ? extends Enum < ? > > determineEnumTypeForBoundField ( Field next ) {
2016-03-18 19:41:43 +01:00
@SuppressWarnings ( " unchecked " )
Class < ? extends Enum < ? > > enumType = ( Class < ? extends Enum < ? > > ) ReflectionUtil . getGenericCollectionTypeOfFieldWithSecondOrderForList ( next ) ;
return enumType ;
}
2015-02-12 17:32:00 -05:00
private String scanPrimitiveDatatype ( Class < ? extends IPrimitiveType < ? > > theClass , DatatypeDef theDatatypeDefinition ) {
2014-02-22 15:33:02 -05:00
ourLog . debug ( " Scanning resource class: {} " , theClass . getName ( ) ) ;
String resourceName = theDatatypeDefinition . name ( ) ;
if ( isBlank ( resourceName ) ) {
throw new ConfigurationException ( " Resource type @ " + ResourceDef . class . getSimpleName ( ) + " annotation contains no resource name: " + theClass . getCanonicalName ( ) ) ;
2014-02-21 21:06:11 -05:00
}
2014-02-22 15:33:02 -05:00
2016-06-06 11:15:11 -07:00
BaseRuntimeElementDefinition < ? > elementDef ;
2014-02-22 15:33:02 -05:00
if ( theClass . equals ( XhtmlDt . class ) ) {
@SuppressWarnings ( " unchecked " )
Class < XhtmlDt > clazz = ( Class < XhtmlDt > ) theClass ;
2016-06-06 11:15:11 -07:00
elementDef = new RuntimePrimitiveDatatypeNarrativeDefinition ( resourceName , clazz , isStandardType ( clazz ) ) ;
2015-04-13 08:32:49 -04:00
} else if ( IBaseXhtml . class . isAssignableFrom ( theClass ) ) {
@SuppressWarnings ( " unchecked " )
Class < ? extends IBaseXhtml > clazz = ( Class < ? extends IBaseXhtml > ) theClass ;
2016-06-06 11:15:11 -07:00
elementDef = new RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition ( resourceName , clazz , isStandardType ( clazz ) ) ;
2015-04-13 08:32:49 -04:00
} else if ( IIdType . class . isAssignableFrom ( theClass ) ) {
2016-06-06 11:15:11 -07:00
elementDef = new RuntimeIdDatatypeDefinition ( theDatatypeDefinition , theClass , isStandardType ( theClass ) ) ;
2014-02-22 15:33:02 -05:00
} else {
2016-06-06 11:15:11 -07:00
elementDef = new RuntimePrimitiveDatatypeDefinition ( theDatatypeDefinition , theClass , isStandardType ( theClass ) ) ;
2014-02-22 15:33:02 -05:00
}
2016-06-06 11:15:11 -07:00
myClassToElementDefinitions . put ( theClass , elementDef ) ;
2015-07-05 17:14:15 -04:00
if ( ! theDatatypeDefinition . isSpecialization ( ) ) {
2015-12-30 11:27:12 -06:00
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 {
2016-06-06 11:15:11 -07:00
myNameToElementDefinitions . put ( resourceName , elementDef ) ;
2015-12-30 11:27:12 -06:00
}
2015-07-05 17:14:15 -04:00
}
2014-02-22 15:33:02 -05:00
return resourceName ;
}
2014-12-10 17:40:47 -05:00
private String scanResource ( Class < ? extends IBaseResource > theClass , ResourceDef resourceDefinition ) {
2014-02-22 15:33:02 -05:00
ourLog . debug ( " Scanning resource class: {} " , theClass . getName ( ) ) ;
2014-08-16 17:46:30 -04:00
boolean primaryNameProvider = true ;
2014-02-22 15:33:02 -05:00
String resourceName = resourceDefinition . name ( ) ;
if ( isBlank ( resourceName ) ) {
2014-08-16 17:46:30 -04:00
Class < ? > parent = theClass . getSuperclass ( ) ;
2014-08-26 08:10:15 -04:00
primaryNameProvider = false ;
while ( parent . equals ( Object . class ) = = false & & isBlank ( resourceName ) ) {
2016-02-26 18:16:35 -05:00
ResourceDef nextDef = pullAnnotation ( parent , ResourceDef . class ) ;
2014-08-16 17:46:30 -04:00
if ( nextDef ! = null ) {
resourceName = nextDef . name ( ) ;
}
parent = parent . getSuperclass ( ) ;
}
if ( isBlank ( resourceName ) ) {
2016-02-26 18:16:35 -05:00
throw new ConfigurationException ( " Resource type @ " + ResourceDef . class . getSimpleName ( ) + " annotation contains no resource name(): " + theClass . getCanonicalName ( )
+ " - This is only allowed for types that extend other resource types " ) ;
2014-02-22 15:33:02 -05:00
}
}
2016-08-14 13:05:16 -04:00
String resourceNameLowerCase = resourceName . toLowerCase ( ) ;
Class < ? extends IBaseResource > builtInType = myNameToResourceType . get ( resourceNameLowerCase ) ;
2016-03-22 11:46:08 +01:00
boolean standardType = builtInType ! = null & & builtInType . equals ( theClass ) = = true ;
if ( primaryNameProvider ) {
if ( builtInType ! = null & & builtInType . equals ( theClass ) = = false ) {
primaryNameProvider = false ;
}
}
2014-03-11 08:39:55 -04:00
String resourceId = resourceDefinition . id ( ) ;
2015-02-14 13:27:11 -05:00
if ( ! isBlank ( resourceId ) ) {
2014-03-13 17:33:12 -04:00
if ( myIdToResourceDefinition . containsKey ( resourceId ) ) {
2016-02-26 18:16:35 -05:00
throw new ConfigurationException ( " The following resource types have the same ID of ' " + resourceId + " ' - " + theClass . getCanonicalName ( ) + " and "
+ myIdToResourceDefinition . get ( resourceId ) . getImplementingClass ( ) . getCanonicalName ( ) ) ;
2014-03-13 17:33:12 -04:00
}
2014-03-11 08:39:55 -04:00
}
2014-08-15 15:55:39 -04:00
2016-06-06 11:15:11 -07:00
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition ( myContext , resourceName , theClass , resourceDefinition , standardType , myClassToElementDefinitions ) ;
2014-02-22 15:33:02 -05:00
myClassToElementDefinitions . put ( theClass , resourceDef ) ;
2014-08-16 17:46:30 -04:00
if ( primaryNameProvider ) {
2015-02-14 13:27:11 -05:00
if ( resourceDef . getStructureVersion ( ) = = myVersion ) {
2016-08-14 13:05:16 -04:00
myNameToResourceDefinitions . put ( resourceNameLowerCase , resourceDef ) ;
2014-12-21 18:13:37 -05:00
}
2014-08-16 17:46:30 -04:00
}
2014-02-22 15:33:02 -05:00
2014-03-11 08:39:55 -04:00
myIdToResourceDefinition . put ( resourceId , resourceDef ) ;
2014-05-07 08:18:43 -04:00
2014-05-05 05:57:43 -07:00
scanResourceForSearchParams ( theClass , resourceDef ) ;
2014-05-07 08:18:43 -04:00
2016-08-14 13:05:16 -04:00
/ *
* 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 ) ;
2017-01-13 21:46:16 -06:00
2014-02-22 15:33:02 -05:00
return resourceName ;
2014-02-21 21:06:11 -05:00
}
2014-12-10 17:40:47 -05:00
private void scanResourceForSearchParams ( Class < ? extends IBaseResource > theClass , RuntimeResourceDefinition theResourceDef ) {
2014-05-07 08:18:43 -04:00
2014-07-29 09:10:25 -04:00
Map < String , RuntimeSearchParam > nameToParam = new HashMap < String , RuntimeSearchParam > ( ) ;
Map < Field , SearchParamDefinition > compositeFields = new LinkedHashMap < Field , SearchParamDefinition > ( ) ;
2017-01-13 21:19:19 -06:00
/ *
* 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 < Field > fields = new HashSet < Field > ( Arrays . asList ( theClass . getFields ( ) ) ) ;
Class < ? > nextClass = theClass ;
do {
for ( Class < ? > nextInterface : nextClass . getInterfaces ( ) ) {
fields . addAll ( Arrays . asList ( nextInterface . getFields ( ) ) ) ;
}
nextClass = nextClass . getSuperclass ( ) ;
} while ( nextClass . equals ( Object . class ) = = false ) ;
/ *
* Now scan the fields for search params
* /
for ( Field nextField : fields ) {
2016-02-26 18:16:35 -05:00
SearchParamDefinition searchParam = pullAnnotation ( nextField , SearchParamDefinition . class ) ;
2014-05-05 05:57:43 -07:00
if ( searchParam ! = null ) {
2016-03-14 06:31:45 -04:00
RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum . forCode ( searchParam . type ( ) . toLowerCase ( ) ) ;
2014-05-28 16:07:53 -04:00
if ( paramType = = null ) {
2015-02-13 18:01:55 -05:00
throw new ConfigurationException ( " Search param " + searchParam . name ( ) + " has an invalid type: " + searchParam . type ( ) ) ;
2014-05-13 18:59:18 -04:00
}
2016-03-14 06:31:45 -04:00
Set < String > providesMembershipInCompartments = null ;
providesMembershipInCompartments = new HashSet < String > ( ) ;
for ( Compartment next : searchParam . providesMembershipIn ( ) ) {
if ( paramType ! = RestSearchParameterTypeEnum . REFERENCE ) {
2016-04-03 18:14:31 -04:00
StringBuilder b = new StringBuilder ( ) ;
b . append ( " Search param " ) ;
b . append ( searchParam . name ( ) ) ;
b . append ( " on resource type " ) ;
b . append ( theClass . getName ( ) ) ;
b . append ( " provides compartment membership but is not of type 'reference' " ) ;
ourLog . warn ( b . toString ( ) ) ;
continue ;
// throw new ConfigurationException(b.toString());
2016-03-14 06:31:45 -04:00
}
providesMembershipInCompartments . add ( next . name ( ) ) ;
}
2015-03-24 18:39:19 -04:00
if ( paramType = = RestSearchParameterTypeEnum . COMPOSITE ) {
2014-07-29 09:10:25 -04:00
compositeFields . put ( nextField , searchParam ) ;
continue ;
}
2016-02-26 18:16:35 -05:00
2017-02-01 05:55:35 -05:00
RuntimeSearchParam param = new RuntimeSearchParam ( searchParam . name ( ) , searchParam . description ( ) , searchParam . path ( ) , paramType , providesMembershipInCompartments , toTargetList ( searchParam . target ( ) ) , RuntimeSearchParamStatusEnum . ACTIVE ) ;
2014-05-05 05:57:43 -07:00
theResourceDef . addSearchParam ( param ) ;
2014-07-29 09:10:25 -04:00
nameToParam . put ( param . getName ( ) , param ) ;
2014-05-05 05:57:43 -07:00
}
}
2014-05-07 08:18:43 -04:00
2014-07-29 09:10:25 -04:00
for ( Entry < Field , SearchParamDefinition > nextEntry : compositeFields . entrySet ( ) ) {
SearchParamDefinition searchParam = nextEntry . getValue ( ) ;
2014-08-15 15:55:39 -04:00
2014-07-29 09:10:25 -04:00
List < RuntimeSearchParam > compositeOf = new ArrayList < RuntimeSearchParam > ( ) ;
2014-08-15 15:55:39 -04:00
for ( String nextName : searchParam . compositeOf ( ) ) {
2014-07-29 09:10:25 -04:00
RuntimeSearchParam param = nameToParam . get ( nextName ) ;
2014-08-15 15:55:39 -04:00
if ( param = = null ) {
2016-02-26 18:16:35 -05:00
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 ( ) } ) ;
2014-07-29 09:10:25 -04:00
continue ;
}
compositeOf . add ( param ) ;
}
2017-02-01 05:55:35 -05:00
RuntimeSearchParam param = new RuntimeSearchParam ( searchParam . name ( ) , searchParam . description ( ) , searchParam . path ( ) , RestSearchParameterTypeEnum . COMPOSITE , compositeOf , null , toTargetList ( searchParam . target ( ) ) , RuntimeSearchParamStatusEnum . ACTIVE ) ;
2014-07-29 09:10:25 -04:00
theResourceDef . addSearchParam ( param ) ;
}
2014-05-05 05:57:43 -07:00
}
2016-05-02 11:42:55 -04:00
private Set < String > toTargetList ( Class < ? extends IBaseResource > [ ] theTarget ) {
HashSet < String > retVal = new HashSet < String > ( ) ;
for ( Class < ? extends IBaseResource > nextType : theTarget ) {
ResourceDef resourceDef = nextType . getAnnotation ( ResourceDef . class ) ;
if ( resourceDef ! = null ) {
retVal . add ( resourceDef . name ( ) ) ;
}
}
return retVal ;
}
2014-03-11 08:39:55 -04:00
private static Class < ? > getGenericCollectionTypeOfCodedField ( Field next ) {
Class < ? > type ;
ParameterizedType collectionType = ( ParameterizedType ) next . getGenericType ( ) ;
Type firstArg = collectionType . getActualTypeArguments ( ) [ 0 ] ;
if ( ParameterizedType . class . isAssignableFrom ( firstArg . getClass ( ) ) ) {
ParameterizedType pt = ( ( ParameterizedType ) firstArg ) ;
firstArg = pt . getActualTypeArguments ( ) [ 0 ] ;
type = ( Class < ? > ) firstArg ;
} else {
type = ( Class < ? > ) firstArg ;
}
return type ;
}
2016-08-14 13:05:16 -04:00
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 ) {
2015-05-02 14:04:43 -07:00
Set < Class < ? extends IBase > > retVal = new HashSet < Class < ? extends IBase > > ( ) ;
2015-05-31 16:52:31 -04:00
2016-08-14 13:05:16 -04:00
InputStream str = theVersion . getVersionImplementation ( ) . getFhirVersionPropertiesFile ( ) ;
2015-02-14 13:27:11 -05:00
Properties prop = new Properties ( ) ;
try {
prop . load ( str ) ;
for ( Entry < Object , Object > nextEntry : prop . entrySet ( ) ) {
String nextKey = nextEntry . getKey ( ) . toString ( ) ;
String nextValue = nextEntry . getValue ( ) . toString ( ) ;
if ( nextKey . startsWith ( " datatype. " ) ) {
if ( theDatatypes ! = null ) {
try {
// Datatypes
2015-05-31 16:52:31 -04:00
2015-05-02 14:04:43 -07:00
@SuppressWarnings ( " unchecked " )
Class < ? extends IBase > dtType = ( Class < ? extends IBase > ) Class . forName ( nextValue ) ;
2016-08-14 13:05:16 -04:00
if ( theExistingElementDefinitions . containsKey ( dtType ) ) {
continue ;
}
2015-05-02 14:04:43 -07:00
retVal . add ( dtType ) ;
2015-05-31 16:52:31 -04:00
2015-02-16 11:33:46 -05:00
if ( IElement . class . isAssignableFrom ( dtType ) ) {
@SuppressWarnings ( " unchecked " )
Class < ? extends IElement > nextClass = ( Class < ? extends IElement > ) dtType ;
theDatatypes . add ( nextClass ) ;
} else if ( IBaseDatatype . class . isAssignableFrom ( dtType ) ) {
@SuppressWarnings ( " unchecked " )
Class < ? extends IBaseDatatype > nextClass = ( Class < ? extends IBaseDatatype > ) dtType ;
theDatatypes . add ( nextClass ) ;
} else {
ourLog . warn ( " Class is not assignable from " + IElement . class . getSimpleName ( ) + " or " + IBaseDatatype . class . getSimpleName ( ) + " : " + nextValue ) ;
2015-02-14 13:27:11 -05:00
continue ;
}
} catch ( ClassNotFoundException e ) {
2015-05-05 18:16:22 -04:00
throw new ConfigurationException ( " Unknown class[ " + nextValue + " ] for data type definition: " + nextKey . substring ( " datatype. " . length ( ) ) , e ) ;
2015-02-14 13:27:11 -05:00
}
}
} else if ( nextKey . startsWith ( " resource. " ) ) {
// Resources
String resName = nextKey . substring ( " resource. " . length ( ) ) . toLowerCase ( ) ;
try {
@SuppressWarnings ( " unchecked " )
Class < ? extends IBaseResource > nextClass = ( Class < ? extends IBaseResource > ) Class . forName ( nextValue ) ;
2016-08-14 13:05:16 -04:00
if ( theExistingElementDefinitions . containsKey ( nextClass ) ) {
continue ;
}
2015-02-14 13:27:11 -05:00
if ( ! IBaseResource . class . isAssignableFrom ( nextClass ) ) {
2015-05-05 18:16:22 -04:00
throw new ConfigurationException ( " Class is not assignable from " + IBaseResource . class . getSimpleName ( ) + " : " + nextValue ) ;
2015-02-14 13:27:11 -05:00
}
theResourceTypes . put ( resName , nextClass ) ;
} catch ( ClassNotFoundException e ) {
2015-05-05 18:16:22 -04:00
throw new ConfigurationException ( " Unknown class[ " + nextValue + " ] for resource definition: " + nextKey . substring ( " resource. " . length ( ) ) , e ) ;
2015-02-14 13:27:11 -05:00
}
} else {
2015-05-05 18:16:22 -04:00
throw new ConfigurationException ( " Unexpected property in version property file: " + nextKey + " = " + nextValue ) ;
2015-02-14 13:27:11 -05:00
}
}
} catch ( IOException e ) {
throw new ConfigurationException ( " Failed to load model property file from classpath: " + " /ca/uhn/fhir/model/dstu/model.properties " ) ;
2016-06-16 07:42:06 -04:00
} finally {
IOUtils . closeQuietly ( str ) ;
2015-02-14 13:27:11 -05:00
}
2015-05-31 16:52:31 -04:00
return retVal ;
2015-04-20 07:31:45 -04:00
}
2014-02-17 17:58:32 -05:00
}