View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import java.lang.reflect.Field;
24  import java.util.*;
25  
26  import org.apache.commons.lang3.StringUtils;
27  import org.hl7.fhir.instance.model.api.*;
28  
29  import ca.uhn.fhir.model.api.annotation.Child;
30  import ca.uhn.fhir.model.api.annotation.Description;
31  
32  public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefinition {
33  
34  	private List<Class<? extends IBase>> myChoiceTypes;
35  	private Map<String, BaseRuntimeElementDefinition<?>> myNameToChildDefinition;
36  	private Map<Class<? extends IBase>, String> myDatatypeToElementName;
37  	private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition;
38  	private String myReferenceSuffix;
39  	private List<Class<? extends IBaseResource>> myResourceTypes;
40  
41  	/**
42  	 * Constructor
43  	 */
44  	public RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation, List<Class<? extends IBase>> theChoiceTypes) {
45  		super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
46  
47  		myChoiceTypes = Collections.unmodifiableList(theChoiceTypes);
48  	}
49  
50  	/**
51  	 * Constructor
52  	 * 
53  	 * For extension, if myChoiceTypes will be set some other way
54  	 */
55  	RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation) {
56  		super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName);
57  	}
58  
59  	void setChoiceTypes(List<Class<? extends IBase>> theChoiceTypes) {
60  		myChoiceTypes = Collections.unmodifiableList(theChoiceTypes);
61  	}
62  
63  	public List<Class<? extends IBase>> getChoices() {
64  		return myChoiceTypes;
65  	}
66  
67  	@Override
68  	public Set<String> getValidChildNames() {
69  		return myNameToChildDefinition.keySet();
70  	}
71  
72  	@Override
73  	public BaseRuntimeElementDefinition<?> getChildByName(String theName) {
74  		assert myNameToChildDefinition.containsKey(theName);
75  
76  		return myNameToChildDefinition.get(theName);
77  	}
78  
79  	@SuppressWarnings("unchecked")
80  	@Override
81  	void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
82  		myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>();
83  		myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>();
84  		myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>();
85  		myResourceTypes = new ArrayList<Class<? extends IBaseResource>>();
86  
87  		myReferenceSuffix = "Reference";
88  
89  		for (Class<? extends IBase> next : myChoiceTypes) {
90  
91  			String elementName = null;
92  			BaseRuntimeElementDefinition<?> nextDef;
93  			boolean nonPreferred = false;
94  			if (IBaseResource.class.isAssignableFrom(next)) {
95  				elementName = getElementName() + StringUtils.capitalize(next.getSimpleName());
96  				List<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
97  				types.add((Class<? extends IBaseResource>) next);
98  				nextDef = findResourceReferenceDefinition(theClassToElementDefinitions);
99  
100 				myNameToChildDefinition.put(getElementName() + "Reference", nextDef);
101 				myNameToChildDefinition.put(getElementName() + "Resource", nextDef);
102 				
103 				myResourceTypes.add((Class<? extends IBaseResource>) next);
104 
105 			} else {
106 				nextDef = theClassToElementDefinitions.get(next);
107 				BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef;
108 
109 				/*
110 				 * In HAPI 1.3 the following applied:
111 				 * Elements which are called foo[x] and have a choice which is a profiled datatype must use the
112 				 * unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the
113 				 * element fooString when encoded, because markdown is a profile of string. This is according to the
114 				 * FHIR spec
115 				 * 
116 				 * Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion
117 				 * with Grahame.
118 				 */
119 				if (nextDef instanceof IRuntimeDatatypeDefinition) {
120 					IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
121 					if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) {
122 						nextDefForChoice = null;
123 						nonPreferred = true;
124 						Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf();
125 						BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType);
126 						elementName = getElementName() + StringUtils.capitalize(elementDef.getName());
127 					}
128 				}
129 				if (nextDefForChoice != null) {
130 					elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName());
131 				}
132 			}
133 
134 			// I don't see how elementName could be null here, but eclipse complains..
135 			if (elementName != null) {
136 				if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) {
137 					myNameToChildDefinition.put(elementName, nextDef);
138 				}
139 			}
140 
141 			/*
142 			 * If this is a resource reference, the element name is "fooNameReference"
143 			 */
144 			if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) {
145 				next = theContext.getVersion().getResourceReferenceType();
146 				elementName = getElementName() + myReferenceSuffix;
147 				myNameToChildDefinition.put(elementName, nextDef);
148 			}
149 
150 			myDatatypeToElementDefinition.put(next, nextDef);
151 
152 			if (myDatatypeToElementName.containsKey(next)) {
153 				String existing = myDatatypeToElementName.get(next);
154 				if (!existing.equals(elementName)) {
155 					throw new ConfigurationException("Already have element name " + existing + " for datatype " + next.getSimpleName() + " in " + getElementName() + ", cannot add " + elementName);
156 				}
157 			} else {
158 				myDatatypeToElementName.put(next, elementName);
159 			}
160 		}
161 
162 		myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition);
163 		myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName);
164 		myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition);
165 		myResourceTypes = Collections.unmodifiableList(myResourceTypes);
166 	}
167 
168 
169 	public List<Class<? extends IBaseResource>> getResourceTypes() {
170 		return myResourceTypes;
171 	}
172 
173 	@Override
174 	public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
175 		String retVal = myDatatypeToElementName.get(theDatatype);
176 		return retVal;
177 	}
178 
179 	@Override
180 	public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) {
181 		return myDatatypeToElementDefinition.get(theDatatype);
182 	}
183 
184 	public Set<Class<? extends IBase>> getValidChildTypes() {
185 		return Collections.unmodifiableSet((myDatatypeToElementDefinition.keySet()));
186 	}
187 
188 }