Start adding ability to override fields in extended definition classes

This commit is contained in:
jamesagnew 2014-08-15 09:19:07 -04:00
parent 901ce12e8e
commit d5c6623da0
11 changed files with 299 additions and 61 deletions

View File

@ -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;
@ -69,39 +70,61 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
if (theDescriptionAnnotation != null) {
myShortDefinition = theDescriptionAnnotation.shortDefinition();
myFormalDefinition = theDescriptionAnnotation.formalDefinition();
}else {
myShortDefinition=null;
myFormalDefinition=null;
} else {
myShortDefinition = null;
myFormalDefinition = null;
}
// TODO: handle lists (max>0), and maybe max=0?
Class<?> declaringClass = myField.getDeclaringClass();
final Class<?> targetReturnType = myField.getType();
try {
String elementName = myElementName;
if ("class".equals(elementName.toLowerCase())) {
elementName = "classElement"; // because getClass() is reserved
}
final Method accessor = BeanUtils.findAccessor(declaringClass, targetReturnType, elementName);
if (accessor==null) {
throw new ConfigurationException("Could not find bean accessor/getter for property " + elementName + " on class " + declaringClass.getCanonicalName());
// 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;
}
}
final Method mutator = findMutator(declaringClass, targetReturnType, elementName);
if (mutator==null) {
throw new ConfigurationException("Could not find bean mutator/setter for property " + elementName + " on class " + declaringClass.getCanonicalName() + " (expected return type " + targetReturnType.getCanonicalName() + ")");
}
if (List.class.isAssignableFrom(targetReturnType)) {
myAccessor = new ListAccessor(accessor);
myMutator = new ListMutator(mutator);
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 PlainAccessor(accessor);
myMutator = new PlainMutator(targetReturnType, mutator);
myAccessor = new FieldPlainAccessor();
myMutator = new FieldPlainMutator();
}
} else {
Class<?> declaringClass = myField.getDeclaringClass();
final Class<?> targetReturnType = myField.getType();
try {
String elementName = myElementName;
if ("class".equals(elementName.toLowerCase())) {
elementName = "classElement"; // because getClass() is reserved
}
final Method accessor = BeanUtils.findAccessor(declaringClass, targetReturnType, elementName);
if (accessor == null) {
throw new ConfigurationException("Could not find bean accessor/getter for property " + elementName + " on class " + declaringClass.getCanonicalName());
}
final Method mutator = findMutator(declaringClass, targetReturnType, elementName);
if (mutator == null) {
throw new ConfigurationException("Could not find bean mutator/setter for property " + elementName + " on class " + declaringClass.getCanonicalName() + " (expected return type " + targetReturnType.getCanonicalName() + ")");
}
if (List.class.isAssignableFrom(targetReturnType)) {
myAccessor = new ListAccessor(accessor);
myMutator = new ListMutator(mutator);
} else {
myAccessor = new PlainAccessor(accessor);
myMutator = new PlainMutator(targetReturnType, mutator);
}
} catch (NoSuchFieldException e) {
throw new ConfigurationException(e);
}
} 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;

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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 {
@ -1126,23 +1124,23 @@ class ParserState<T> {
@Override
public void wereBack() {
myObject = (T) myInstance;
/*
* Stitch together resource references
*/
Map<String, IResource> idToResource = new HashMap<String, IResource>();
List<IResource> resources = myInstance.toListOfResources();
for (IResource next : resources) {
if (next.getId() != null && next.getId().isEmpty()==false) {
if (next.getId() != null && next.getId().isEmpty() == false) {
idToResource.put(next.getId().toUnqualifiedVersionless().getValue(), next);
}
}
for (IResource next : resources) {
List<ResourceReferenceDt> refs = myContext.newTerser().getAllPopulatedChildElementsOfType(next, ResourceReferenceDt.class);
for (ResourceReferenceDt nextRef : refs) {
if (nextRef.isEmpty()==false && nextRef.getReference() != null) {
if (nextRef.isEmpty() == false && nextRef.getReference() != null) {
IResource target = idToResource.get(nextRef.getReference().getValue());
if (target != null) {
nextRef.setResource(target);
@ -1150,7 +1148,7 @@ class ParserState<T> {
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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"/>

View File

@ -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();
});