Start adding ability to override fields in extended definition classes
This commit is contained in:
parent
901ce12e8e
commit
d5c6623da0
|
@ -37,7 +37,8 @@ import ca.uhn.fhir.model.api.annotation.Description;
|
|||
import ca.uhn.fhir.util.BeanUtils;
|
||||
|
||||
public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChildDefinition {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseRuntimeDeclaredChildDefinition.class);
|
||||
private Boolean ourUseMethodAccessors;
|
||||
private final IAccessor myAccessor;
|
||||
private final String myElementName;
|
||||
private final Field myField;
|
||||
|
@ -76,6 +77,27 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
|
|||
|
||||
// TODO: handle lists (max>0), and maybe max=0?
|
||||
|
||||
// TODO: finish implementing field level accessors/mutators
|
||||
if (ourUseMethodAccessors == null) {
|
||||
try {
|
||||
myField.setAccessible(true);
|
||||
ourUseMethodAccessors = true;
|
||||
} catch (SecurityException e) {
|
||||
ourLog.info("Can not use field accessors/mutators, going to use methods instead");
|
||||
ourUseMethodAccessors = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ourUseMethodAccessors == false) {
|
||||
if (List.class.equals(myField.getType())) {
|
||||
// TODO: verify that generic type is IElement
|
||||
myAccessor = new FieldListAccessor();
|
||||
myMutator = new FieldListMutator();
|
||||
} else {
|
||||
myAccessor = new FieldPlainAccessor();
|
||||
myMutator = new FieldPlainMutator();
|
||||
}
|
||||
} else {
|
||||
Class<?> declaringClass = myField.getDeclaringClass();
|
||||
final Class<?> targetReturnType = myField.getType();
|
||||
try {
|
||||
|
@ -103,6 +125,7 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
|
|||
} catch (NoSuchFieldException e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -160,6 +183,77 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
|
|||
}
|
||||
}
|
||||
|
||||
private final class FieldPlainMutator implements IMutator {
|
||||
@Override
|
||||
public void addValue(Object theTarget, IElement theValue) {
|
||||
try {
|
||||
myField.set(theTarget, theValue);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigurationException("Failed to set value", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ConfigurationException("Failed to set value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class FieldPlainAccessor implements IAccessor {
|
||||
@Override
|
||||
public List<? extends IElement> getValues(Object theTarget) {
|
||||
try {
|
||||
Object values = myField.get(theTarget);
|
||||
if (values == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends IElement> retVal = (List<? extends IElement>) Collections.singletonList(values);
|
||||
return retVal;
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigurationException("Failed to get value", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ConfigurationException("Failed to get value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class FieldListMutator implements IMutator {
|
||||
@Override
|
||||
public void addValue(Object theTarget, IElement theValue) {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<IElement> existingList = (List<IElement>) myField.get(theTarget);
|
||||
if (existingList == null) {
|
||||
existingList = new ArrayList<IElement>(2);
|
||||
myField.set(theTarget, existingList);
|
||||
}
|
||||
existingList.add(theValue);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigurationException("Failed to set value", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ConfigurationException("Failed to set value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class FieldListAccessor implements IAccessor {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<? extends IElement> getValues(Object theTarget) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends IElement> retVal;
|
||||
try {
|
||||
retVal = (List<? extends IElement>) myField.get(theTarget);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigurationException("Failed to get value", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ConfigurationException("Failed to get value", e);
|
||||
}
|
||||
if (retVal == null) {
|
||||
retVal = Collections.emptyList();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class ListAccessor implements IAccessor {
|
||||
private final Method myAccessorMethod;
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.apache.commons.lang3.text.WordUtils;
|
|||
import ca.uhn.fhir.model.api.IElement;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.view.ViewGenerator;
|
||||
import ca.uhn.fhir.narrative.INarrativeGenerator;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.parser.JsonParser;
|
||||
|
@ -297,4 +298,8 @@ public class FhirContext {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public ViewGenerator newViewGenerator() {
|
||||
return new ViewGenerator(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -308,6 +308,11 @@ class ModelScanner {
|
|||
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderToExtensionDef = new TreeMap<Integer, BaseRuntimeDeclaredChildDefinition>();
|
||||
|
||||
LinkedList<Class<? extends ICompositeElement>> classes = new LinkedList<Class<? extends ICompositeElement>>();
|
||||
|
||||
/*
|
||||
* We scan classes for annotated fields in the class but also all of its
|
||||
* superclasses
|
||||
*/
|
||||
Class<? extends ICompositeElement> current = theClass;
|
||||
do {
|
||||
classes.push(current);
|
||||
|
@ -365,8 +370,20 @@ class ModelScanner {
|
|||
|
||||
Description descriptionAnnotation = next.getAnnotation(Description.class);
|
||||
|
||||
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
|
||||
Extension extensionAttr = next.getAnnotation(Extension.class);
|
||||
if (extensionAttr != null) {
|
||||
orderMap = theOrderToExtensionDef;
|
||||
}
|
||||
|
||||
String elementName = childAnnotation.name();
|
||||
int order = childAnnotation.order();
|
||||
if (order == Child.REPLACE_PARENT) {
|
||||
if (true) continue; // TODO: finish implementing
|
||||
for (Entry<Integer, BaseRuntimeDeclaredChildDefinition> nextEntry : orderMap.entrySet()) {
|
||||
|
||||
}
|
||||
}
|
||||
if (order < 0 && order != Child.ORDER_UNKNOWN) {
|
||||
throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + next.getName() + "' on target type: " + theClass);
|
||||
}
|
||||
|
@ -375,13 +392,6 @@ class ModelScanner {
|
|||
}
|
||||
int min = childAnnotation.min();
|
||||
int max = childAnnotation.max();
|
||||
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> orderMap = theOrderToElementDef;
|
||||
|
||||
Extension extensionAttr = next.getAnnotation(Extension.class);
|
||||
if (extensionAttr != null) {
|
||||
orderMap = theOrderToExtensionDef;
|
||||
}
|
||||
|
||||
/*
|
||||
* Anything that's marked as unknown is given a new ID that is <0 so that it doesn't conflict wityh any
|
||||
* given IDs and can be figured out later
|
||||
|
|
|
@ -48,8 +48,9 @@ public @interface Child {
|
|||
|
||||
/**
|
||||
* Constant value to supply for {@link #order()} to indicate that this child should replace the
|
||||
* entry in the superclass with the same name. This may be used to indicate to parsers that
|
||||
*
|
||||
* entry in the superclass with the same name (and take its {@link Child#order() order} value
|
||||
* in the process). This is useful if you wish to redefine an existing field in a resource/type
|
||||
* definition in order to constrain/extend it.
|
||||
*/
|
||||
int REPLACE_PARENT = -2;
|
||||
|
||||
|
|
|
@ -15,10 +15,16 @@ import ca.uhn.fhir.model.api.IResource;
|
|||
|
||||
public class ViewGenerator {
|
||||
|
||||
public <T extends IResource> T newView(FhirContext theContext, IResource theResource, Class<T> theTargetType) {
|
||||
private FhirContext myCtx;
|
||||
|
||||
public ViewGenerator(FhirContext theFhirContext) {
|
||||
myCtx=theFhirContext;
|
||||
}
|
||||
|
||||
public <T extends IResource> T newView(IResource theResource, Class<T> theTargetType) {
|
||||
Class<? extends IResource> sourceType = theResource.getClass();
|
||||
RuntimeResourceDefinition sourceDef = theContext.getResourceDefinition(theResource);
|
||||
RuntimeResourceDefinition targetDef = theContext.getResourceDefinition(theTargetType);
|
||||
RuntimeResourceDefinition sourceDef = myCtx.getResourceDefinition(theResource);
|
||||
RuntimeResourceDefinition targetDef = myCtx.getResourceDefinition(theTargetType);
|
||||
|
||||
if (sourceType.equals(theTargetType)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -45,14 +51,19 @@ public class ViewGenerator {
|
|||
List<BaseRuntimeChildDefinition> targetChildren = theTargetDef.getChildren();
|
||||
for (BaseRuntimeChildDefinition nextChild : targetChildren) {
|
||||
|
||||
BaseRuntimeChildDefinition sourceChildEquivalent = theSourceDef.getChildByNameOrThrowDataFormatException(nextChild.getElementName());
|
||||
String elementName = nextChild.getElementName();
|
||||
if (nextChild.getValidChildNames().size() > 1) {
|
||||
elementName = nextChild.getValidChildNames().iterator().next();
|
||||
}
|
||||
|
||||
BaseRuntimeChildDefinition sourceChildEquivalent = theSourceDef.getChildByNameOrThrowDataFormatException(elementName);
|
||||
if (sourceChildEquivalent == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<? extends IElement> sourceValues = sourceChildEquivalent.getAccessor().getValues(theTarget);
|
||||
List<? extends IElement> sourceValues = sourceChildEquivalent.getAccessor().getValues(theSource);
|
||||
for (IElement nextElement : sourceValues) {
|
||||
nextChild.getMutator().addValue(theTargetDef, nextElement);
|
||||
nextChild.getMutator().addValue(theTarget, nextElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -158,7 +158,8 @@ class ParserState<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically intended for embedded XHTML content
|
||||
* Invoked after any new XML event is individually processed, containing a copy of the XML event. This is basically
|
||||
* intended for embedded XHTML content
|
||||
*/
|
||||
public void xmlEvent(XMLEvent theNextEvent) {
|
||||
myState.xmlEvent(theNextEvent);
|
||||
|
@ -238,7 +239,8 @@ class ParserState<T> {
|
|||
myInstance.setScheme(theValue);
|
||||
} else if ("value".equals(theName)) {
|
||||
/*
|
||||
* This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values instead of one like everything else.
|
||||
* This handles XML parsing, which is odd for this quasi-resource type, since the tag has three values
|
||||
* instead of one like everything else.
|
||||
*/
|
||||
switch (myCatState) {
|
||||
case STATE_LABEL:
|
||||
|
@ -669,11 +671,7 @@ class ParserState<T> {
|
|||
if (myPreResourceState != null && getCurrentElement() instanceof ISupportsUndeclaredExtensions) {
|
||||
ExtensionDt newExtension = new ExtensionDt(theIsModifier, theUrlAttr);
|
||||
ISupportsUndeclaredExtensions elem = (ISupportsUndeclaredExtensions) getCurrentElement();
|
||||
if (theIsModifier) {
|
||||
elem.getUndeclaredModifierExtensions().add(newExtension);
|
||||
} else {
|
||||
elem.getUndeclaredExtensions().add(newExtension);
|
||||
}
|
||||
elem.addUndeclaredExtension(newExtension);
|
||||
ExtensionState newState = new ExtensionState(myPreResourceState, newExtension);
|
||||
push(newState);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package ca.uhn.fhir.model.view;
|
||||
|
||||
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.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
|
||||
@ResourceDef(name = "Patient")
|
||||
public class ExtPatient extends Patient {
|
||||
|
||||
@Extension(url = "urn:ext", isModifier = false, definedLocally = false)
|
||||
@Child(name = "ext")
|
||||
private IntegerDt myExt;
|
||||
|
||||
@Extension(url = "urn:modExt", isModifier = false, definedLocally = false)
|
||||
@Child(name = "modExt")
|
||||
private IntegerDt myModExt;
|
||||
|
||||
public IntegerDt getExt() {
|
||||
if (myExt == null) {
|
||||
myExt = new IntegerDt();
|
||||
}
|
||||
return myExt;
|
||||
}
|
||||
|
||||
public void setExt(IntegerDt theExt) {
|
||||
myExt = theExt;
|
||||
}
|
||||
|
||||
public IntegerDt getModExt() {
|
||||
if (myModExt == null) {
|
||||
myModExt = new IntegerDt();
|
||||
}
|
||||
return myModExt;
|
||||
}
|
||||
|
||||
public void setModExt(IntegerDt theModExt) {
|
||||
myModExt = theModExt;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package ca.uhn.fhir.model.view;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
|
||||
public class ViewGeneratorTest {
|
||||
|
||||
private static FhirContext ourCtx = new FhirContext();
|
||||
|
||||
@Test
|
||||
public void testView() {
|
||||
|
||||
ExtPatient src = new ExtPatient();
|
||||
src.addIdentifier("urn:sys", "id1");
|
||||
src.addIdentifier("urn:sys", "id2");
|
||||
src.getExt().setValue(100);
|
||||
src.getModExt().setValue(200);
|
||||
|
||||
String enc = ourCtx.newXmlParser().encodeResourceToString(src);
|
||||
IParser parser = ourCtx.newXmlParser();
|
||||
Patient nonExt = parser.parseResource(Patient.class, enc);
|
||||
|
||||
assertEquals(Patient.class, nonExt.getClass());
|
||||
assertEquals("urn:sys", nonExt.getIdentifier().get(0).getSystem().getValueAsString());
|
||||
assertEquals("id1", nonExt.getIdentifier().get(0).getValue().getValue());
|
||||
assertEquals("urn:sys", nonExt.getIdentifier().get(1).getSystem().getValueAsString());
|
||||
assertEquals("id2", nonExt.getIdentifier().get(1).getValue().getValueAsString());
|
||||
|
||||
List<ExtensionDt> ext = nonExt.getUndeclaredExtensionsByUrl("urn:ext");
|
||||
assertEquals(1,ext.size());
|
||||
assertEquals("urn:ext", ext.get(0).getUrlAsString());
|
||||
assertEquals(IntegerDt.class, ext.get(0).getValueAsPrimitive().getClass());
|
||||
assertEquals("100", ext.get(0).getValueAsPrimitive().getValueAsString());
|
||||
|
||||
List<ExtensionDt> modExt = nonExt.getUndeclaredExtensionsByUrl("urn:modExt");
|
||||
assertEquals(1,modExt.size());
|
||||
assertEquals("urn:modExt", modExt.get(0).getUrlAsString());
|
||||
assertEquals(IntegerDt.class, modExt.get(0).getValueAsPrimitive().getClass());
|
||||
assertEquals("200", modExt.get(0).getValueAsPrimitive().getValueAsString());
|
||||
|
||||
ExtPatient va = ourCtx.newViewGenerator().newView(nonExt, ExtPatient.class);
|
||||
assertEquals("urn:sys", va.getIdentifier().get(0).getSystem().getValueAsString());
|
||||
assertEquals("id1", va.getIdentifier().get(0).getValue().getValue());
|
||||
assertEquals("urn:sys", va.getIdentifier().get(1).getSystem().getValueAsString());
|
||||
assertEquals("id2", va.getIdentifier().get(1).getValue().getValueAsString());
|
||||
assertEquals(100, va.getExt().getValue().intValue());
|
||||
assertEquals(200, va.getModExt().getValue().intValue());
|
||||
assertEquals(0, va.getAllUndeclaredExtensions().size());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -51,6 +51,8 @@ import ca.uhn.fhir.model.dstu.resource.Conformance;
|
|||
import ca.uhn.fhir.model.dstu.resource.Conformance.Rest;
|
||||
import ca.uhn.fhir.model.dstu.resource.Conformance.RestQuery;
|
||||
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResource;
|
||||
import ca.uhn.fhir.model.dstu.resource.Conformance.RestResourceSearchParam;
|
||||
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
|
@ -325,6 +327,7 @@ public class Controller {
|
|||
RuntimeResourceDefinition def = myCtx.getResourceDefinition(theRequest.getResource());
|
||||
|
||||
TreeSet<String> includes = new TreeSet<String>();
|
||||
TreeSet<String> sortParams = new TreeSet<String>();
|
||||
List<RestQuery> queries = new ArrayList<Conformance.RestQuery>();
|
||||
boolean haveSearchParams = false;
|
||||
List<List<String>> queryIncludes = new ArrayList<List<String>>();
|
||||
|
@ -336,6 +339,11 @@ public class Controller {
|
|||
includes.add(next.getValue());
|
||||
}
|
||||
}
|
||||
for (RestResourceSearchParam next : nextRes.getSearchParam()) {
|
||||
if (next.getType().getValueAsEnum() != SearchParamTypeEnum.COMPOSITE) {
|
||||
sortParams.add(next.getName().getValue());
|
||||
}
|
||||
}
|
||||
if (nextRes.getSearchParam().size() > 0) {
|
||||
haveSearchParams = true;
|
||||
}
|
||||
|
@ -370,6 +378,7 @@ public class Controller {
|
|||
theModel.put("queries", queries);
|
||||
theModel.put("haveSearchParams", haveSearchParams);
|
||||
theModel.put("queryIncludes", queryIncludes);
|
||||
theModel.put("sortParams", sortParams);
|
||||
|
||||
if (isNotBlank(theRequest.getUpdateId())) {
|
||||
String updateId = theRequest.getUpdateId();
|
||||
|
|
|
@ -145,20 +145,18 @@
|
|||
<div class='col-sm-3'>
|
||||
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-info">Action</button>
|
||||
<input type="hidden" id="search_sort" />
|
||||
<button type="button" class="btn btn-info" id="search_sort_button">Default Sort</button>
|
||||
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Default Sort</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="#">Default Sort</a></li>
|
||||
<li><a href="javascript:updateSort('');">Default Sort</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#">Another action</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
<li th:each="param : ${sortParams}"><a th:href="'javascript:updateSort(\'' + ${param} + '\');'" th:text="${param}"></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br clear="all"/>
|
||||
|
|
|
@ -393,6 +393,15 @@ function setResource(target, resourceName) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateSort(value) {
|
||||
$('#search_sort').val(value);
|
||||
if (value == '') {
|
||||
$('#search_sort_button').text('Default Sort');
|
||||
} else {
|
||||
$('#search_sort_button').text(value);
|
||||
}
|
||||
}
|
||||
|
||||
$( document ).ready(function() {
|
||||
addSearchParamRow();
|
||||
});
|
Loading…
Reference in New Issue