Lots of bug fixes and documentation tweaks - Several failing unit tests checked in, need to get these passing

This commit is contained in:
jamesagnew 2014-05-07 08:18:43 -04:00
parent 9f302937d9
commit 4b7a4ac794
82 changed files with 3762 additions and 1793 deletions

View File

@ -225,6 +225,7 @@
<artifactId>maven-site-plugin</artifactId> <artifactId>maven-site-plugin</artifactId>
<version>3.3</version> <version>3.3</version>
</plugin> </plugin>
<!--
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId> <artifactId>maven-surefire-report-plugin</artifactId>
@ -250,6 +251,7 @@
<artifactId>maven-jxr-plugin</artifactId> <artifactId>maven-jxr-plugin</artifactId>
<version>2.4</version> <version>2.4</version>
</plugin> </plugin>
-->
<!-- <!--
<plugin> <plugin>
<groupId>org.codehaus.mojo</groupId> <groupId>org.codehaus.mojo</groupId>

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.context;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -33,6 +35,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
@ -56,16 +59,11 @@ import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension; import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.api.annotation.SearchParamDefinition; import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
import ca.uhn.fhir.model.dstu.composite.AttachmentDt;
import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.QuantityDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt; import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.ICodedDatatype; import ca.uhn.fhir.model.primitive.ICodedDatatype;
import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.ReflectionUtil;
@ -148,12 +146,42 @@ class ModelScanner {
} }
private void init(Set<Class<? extends IElement>> toScan) { private void init(Set<Class<? extends IElement>> toScan) {
toScan.add(DateDt.class); long start = System.currentTimeMillis();
toScan.add(CodeDt.class);
toScan.add(DecimalDt.class); InputStream str = ModelScanner.class.getResourceAsStream("/ca/uhn/fhir/model/dstu/model.properties");
toScan.add(AttachmentDt.class); if (str == null) {
toScan.add(ResourceReferenceDt.class); str = ModelScanner.class.getResourceAsStream("ca/uhn/fhir/model/dstu/model.properties");
toScan.add(QuantityDt.class); // TODO: why is this required }
if (str == null) {
throw new ConfigurationException("Can not find model property file on classpath: " + "/ca/uhn/fhir/model/dstu/model.properties");
}
Properties prop = new Properties();
try {
prop.load(str);
for (Object nextValue : prop.values()) {
try {
@SuppressWarnings("unchecked")
Class<? extends IElement> nextClass = (Class<? extends IElement>) Class.forName((String) nextValue);
if (!IElement.class.isAssignableFrom(nextClass)) {
ourLog.warn("Class is not assignable from " + IElement.class.getSimpleName()+": " + nextValue);
continue;
}
toScan.add(nextClass);
} catch (ClassNotFoundException e) {
ourLog.warn("Unknown class exception: " + nextValue, e);
}
}
} catch (IOException e) {
throw new ConfigurationException("Failed to load model property file from classpath: " + "/ca/uhn/fhir/model/dstu/model.properties");
}
// toScan.add(DateDt.class);
// toScan.add(CodeDt.class);
// toScan.add(DecimalDt.class);
// toScan.add(AttachmentDt.class);
// toScan.add(ResourceReferenceDt.class);
// toScan.add(QuantityDt.class);
do { do {
for (Class<? extends IElement> nextClass : toScan) { for (Class<? extends IElement> nextClass : toScan) {
@ -176,7 +204,8 @@ class ModelScanner {
myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition(); myRuntimeChildUndeclaredExtensionDefinition = new RuntimeChildUndeclaredExtensionDefinition();
myRuntimeChildUndeclaredExtensionDefinition.sealAndInitialize(myClassToElementDefinitions); myRuntimeChildUndeclaredExtensionDefinition.sealAndInitialize(myClassToElementDefinitions);
ourLog.info("Done scanning FHIR library, found {} model entries", myClassToElementDefinitions.size()); long time = System.currentTimeMillis() - start;
ourLog.info("Done scanning FHIR library, found {} model entries in {}ms", myClassToElementDefinitions.size(), time);
} }
private void scan(Class<? extends IElement> theClass) throws ConfigurationException { private void scan(Class<? extends IElement> theClass) throws ConfigurationException {
@ -315,8 +344,7 @@ class ModelScanner {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef, private void scanCompositeElementForChildren(Class<? extends ICompositeElement> theClass, Set<String> elementNames, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToElementDef, TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
TreeMap<Integer, BaseRuntimeDeclaredChildDefinition> theOrderToExtensionDef) {
int baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1; int baseElementOrder = theOrderToElementDef.isEmpty() ? 0 : theOrderToElementDef.lastEntry().getKey() + 1;
for (Field next : theClass.getDeclaredFields()) { for (Field next : theClass.getDeclaredFields()) {
@ -332,7 +360,7 @@ class ModelScanner {
String elementName = childAnnotation.name(); String elementName = childAnnotation.name();
int order = childAnnotation.order(); int order = childAnnotation.order();
if (order < 0 && order != Child.ORDER_UNKNOWN) { if (order < 0 && order != Child.ORDER_UNKNOWN) {
throw new ConfigurationException("Invalid order '" + order +"' on @Child for field '" + next.getName()+ "' on target type: " + theClass); throw new ConfigurationException("Invalid order '" + order + "' on @Child for field '" + next.getName() + "' on target type: " + theClass);
} }
if (order != Child.ORDER_UNKNOWN) { if (order != Child.ORDER_UNKNOWN) {
order = order + baseElementOrder; order = order + baseElementOrder;
@ -537,7 +565,6 @@ class ModelScanner {
scanResourceForSearchParams(theClass, resourceDef); scanResourceForSearchParams(theClass, resourceDef);
return resourceName; return resourceName;
} }

View File

@ -1,63 +0,0 @@
package ca.uhn.fhir.model.api;
/*
* #%L
* HAPI FHIR Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* 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%
*/
import org.apache.commons.lang3.StringUtils;
public class BundleCategory extends BaseElement implements IElement {
private String myLabel;
private String myScheme;
private String myTerm;
public String getLabel() {
return myLabel;
}
public String getScheme() {
return myScheme;
}
public String getTerm() {
return myTerm;
}
public BundleCategory setLabel(String theLabel) {
myLabel = theLabel;
return this;
}
public BundleCategory setScheme(String theScheme) {
myScheme = theScheme;
return this;
}
public BundleCategory setTerm(String theTerm) {
myTerm = theTerm;
return this;
}
@Override
public boolean isEmpty() {
return StringUtils.isBlank(myLabel) && StringUtils.isBlank(myScheme) && StringUtils.isBlank(myTerm);
}
}

View File

@ -20,9 +20,6 @@ package ca.uhn.fhir.model.api;
* #L% * #L%
*/ */
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
@ -43,7 +40,7 @@ public class BundleEntry extends BaseBundle {
private StringDt myTitle; private StringDt myTitle;
private InstantDt myUpdated; private InstantDt myUpdated;
private XhtmlDt mySummary; private XhtmlDt mySummary;
private List<BundleCategory> myCategories; private TagList myCategories;
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
@ -104,15 +101,15 @@ public class BundleEntry extends BaseBundle {
return mySummary; return mySummary;
} }
public BundleCategory addCategory() { public Tag addCategory() {
BundleCategory retVal = new BundleCategory(); Tag retVal = new Tag();
getCategories().add(retVal); getCategories().add(retVal);
return retVal; return retVal;
} }
public List<BundleCategory> getCategories() { public TagList getCategories() {
if (myCategories == null) { if (myCategories == null) {
myCategories = new ArrayList<BundleCategory>(); myCategories = new TagList();
} }
return myCategories; return myCategories;
} }
@ -127,4 +124,8 @@ public class BundleEntry extends BaseBundle {
myUpdated = theUpdated; myUpdated = theUpdated;
} }
public void addCategory(Tag theTag) {
getCategories().add(theTag);
}
} }

View File

@ -24,6 +24,7 @@ import java.util.Map;
import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
public interface IResource extends ICompositeElement { public interface IResource extends ICompositeElement {
@ -34,27 +35,41 @@ public interface IResource extends ICompositeElement {
* list automatically (placing inline resources in the contained list when * list automatically (placing inline resources in the contained list when
* encoding, and copying contained resources from this list to their * encoding, and copying contained resources from this list to their
* appropriate references when parsing) so it is generally not neccesary to * appropriate references when parsing) so it is generally not neccesary to
* interact with this list directly. * interact with this list directly. Instead, in a server you can place
* resource instances in reference fields (such as {@link Patient#setManagingOrganization(ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt)})
* and the resource will be automatically contained. In a client, contained resources will
* be automatically populated into their appropriate fields by the HAPI parser.
* </p> * </p>
* TODO: document contained resources and link there * TODO: document contained resources and link there
*/ */
ContainedDt getContained(); ContainedDt getContained();
/**
* Returns the narrative block for this resource
*/
NarrativeDt getText(); NarrativeDt getText();
/** /**
* Returns the metadata map for this object, creating it if neccesary. Metadata * Returns the metadata map for this object, creating it if neccesary.
* entries are used to get/set feed bundle entries, such as the * Metadata entries are used to get/set feed bundle entries, such as the
* resource version, or the last updated timestamp. * resource version, or the last updated timestamp.
* <p>
* Keys in this map are enumerated in the {@link ResourceMetadataKeyEnum},
* and each key has a specific value type that it must use.
* </p>
*
* @see ResourceMetadataKeyEnum for a list of allowable keys and the object
* types that values of a given key must use.
*/ */
Map<ResourceMetadataKeyEnum, Object> getResourceMetadata(); Map<ResourceMetadataKeyEnum, Object> getResourceMetadata();
/** /**
* Sets the metadata map for this object. Metadata * Sets the metadata map for this object. Metadata entries are used to
* entries are used to get/set feed bundle entries, such as the * get/set feed bundle entries, such as the resource version, or the last
* resource version, or the last updated timestamp. * updated timestamp.
* *
* @throws NullPointerException The map must not be null * @throws NullPointerException
* The map must not be null
*/ */
void setResourceMetadata(Map<ResourceMetadataKeyEnum, Object> theMap); void setResourceMetadata(Map<ResourceMetadataKeyEnum, Object> theMap);

View File

@ -24,17 +24,21 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.net.URI; import java.net.URI;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
public class Tag { public class Tag extends BaseElement implements IElement {
/** /**
* Convenience constant containing the "http://hl7.org/fhir/tag" scheme * Convenience constant containing the "http://hl7.org/fhir/tag" scheme
* value * value
*/ */
public static final String HL7_ORG_FHIR_TAG = "http://hl7.org/fhir/tag"; public static final String HL7_ORG_FHIR_TAG = "http://hl7.org/fhir/tag";
public static final String ATTR_TERM = "term";
public static final String ATTR_LABEL = "label";
public static final String ATTR_SCHEME = "scheme";
private String myLabel; private String myLabel;
private String myScheme; private String myScheme;
private String myTerm; private String myTerm;
@ -87,15 +91,6 @@ public class Tag {
return true; return true;
} }
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE);
b.append("Term", myTerm);
b.append("Label", myLabel);
b.append("Scheme", myScheme);
return b.toString();
}
public String getLabel() { public String getLabel() {
return myLabel; return myLabel;
} }
@ -118,16 +113,24 @@ public class Tag {
return result; return result;
} }
public void setLabel(String theLabel) { @Override
public boolean isEmpty() {
return StringUtils.isBlank(myLabel) && StringUtils.isBlank(myScheme) && StringUtils.isBlank(myTerm);
}
public Tag setLabel(String theLabel) {
myLabel = theLabel; myLabel = theLabel;
return this;
} }
public void setScheme(String theScheme) { public Tag setScheme(String theScheme) {
myScheme = theScheme; myScheme = theScheme;
return this;
} }
public void setTerm(String theTerm) { public Tag setTerm(String theTerm) {
myTerm = theTerm; myTerm = theTerm;
return this;
} }
public String toHeaderValue() { public String toHeaderValue() {
@ -142,4 +145,13 @@ public class Tag {
return b.toString(); return b.toString();
} }
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE);
b.append("Term", myTerm);
b.append("Label", myLabel);
b.append("Scheme", myScheme);
return b.toString();
}
} }

View File

@ -25,9 +25,18 @@ import java.util.ArrayList;
public class TagList extends ArrayList<Tag> { public class TagList extends ArrayList<Tag> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public static final String ATTR_CATEGORY = "category";
public static final String ELEMENT_NAME = "TagList";
public static final String ELEMENT_NAME_LC = ELEMENT_NAME.toLowerCase();
public void addTag(String theTerm, String theLabel, String theScheme) { public Tag addTag(String theTerm, String theLabel, String theScheme) {
add(new Tag(theTerm, theLabel, theScheme)); Tag retVal = new Tag(theTerm, theLabel, theScheme);
add(retVal);
return retVal;
}
public Tag addTag() {
return addTag(null, null, null);
} }
} }

View File

@ -1,99 +0,0 @@
package ca.uhn.fhir.model.api;
/*
* #%L
* HAPI FHIR Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* 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%
*/
public abstract class ValueSetRegistry {
// private Map<String, Class<? extends Enum>>
// private static final Map<Class<? extends ValueSetRegistry>, CodeMap> myClassToCodeMap = new HashMap<Class<? extends ValueSetRegistry>, ValueSetRegistry.CodeMap>();
//
// public static final ValueSetRegistry OTHER = new OtherCode();
//
// private final String myCode;
// private final int myOrdinal;
//
// public ValueSetRegistry(String theCode, String theValueSetIdentifier) {
// myCode = theCode;
// myOrdinal = ourNextOrdinal++;
//
// CodeMap codeMap = myClassToCodeMap.get(getClass());
// if (codeMap == null) {
// codeMap = new CodeMap(theValueSetIdentifier);
// myClassToCodeMap.put(getClass(), codeMap);
// }
//
// codeMap.addCode(this);
//
// }
//
// public ValueSetRegistry() {
// // TODO Auto-generated constructor stub
// }
//
// public ValueSetRegistry getCode(Class<? extends ValueSetRegistry> theType, String theCode) {
// CodeMap codeMap = myClassToCodeMap.get(theType);
// if (codeMap == null) {
//
// }
// }
//
// public String getCode() {
// return myCode;
// }
//
// public int getOrdinal() {
// return myOrdinal;
// }
//
// private static final class OtherCode extends ValueSetRegistry {
// private OtherCode() {
// super();
// }
// }
//
// private static final class OtherInstance extends ValueSetRegistry {
// private OtherInstance(String theCode) {
// super();
// myCode = theCode;
// }
// }
//
// private static class CodeMap {
// private Map<String, ValueSetRegistry> myCodeMap = new HashMap<String, ValueSetRegistry>();
// private String myValueSetIdentifier;
// private int myNextOrdinal = 0;
//
// public CodeMap(String theValueSetIdentifier) {
// myValueSetIdentifier = theValueSetIdentifier;
// }
//
// public void addCode(ValueSetRegistry theValueSetEnumeration) {
// myCodeMap.put(theValueSetEnumeration.getCode(), theValueSetEnumeration);
// }
//
// public int nextOrdinal() {
// return myNextOrdinal++;
// }
// }
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.model.api.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.annotation.GetTags;
/**
* Parameter annotation for the {@link TagList} parameter in a {@link GetTags},
* {@link AddTags}, or {@link DeleteTags} method.
*
* @see GetTags
* @see AddTags
* @see DeleteTags
*/
@Target(value = ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface TagListParam {
// nothing
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.model.dstu.composite; package ca.uhn.fhir.model.dstu.composite;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
/* /*
* #%L * #%L
* HAPI FHIR Library * HAPI FHIR Library
@ -20,6 +22,7 @@ package ca.uhn.fhir.model.dstu.composite;
* #L% * #L%
*/ */
@DatatypeDef(name="AgeDt")
public class AgeDt extends QuantityDt { public class AgeDt extends QuantityDt {
// TODO: implement restricions // TODO: implement restricions

View File

@ -41,19 +41,16 @@ public class Binary extends BaseResource implements IResource {
@Override @Override
public List<IElement> getAllPopulatedChildElements() { public List<IElement> getAllPopulatedChildElements() {
// TODO Auto-generated method stub
return null; return null;
} }
@Override @Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) { public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
// TODO Auto-generated method stub
return null; return null;
} }
@Override @Override
public ContainedDt getContained() { public ContainedDt getContained() {
// TODO Auto-generated method stub
return null; return null;
} }

View File

@ -31,35 +31,61 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.CodingDt;
@DatatypeDef(name = "CodeableConcept", isSpecialization=true) @DatatypeDef(name = "CodeableConcept", isSpecialization = true)
public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt { public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt {
private IValueSetEnumBinder<T> myBinder; private IValueSetEnumBinder<T> myBinder;
/**
* Constructor
*/
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder) { public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder) {
myBinder = theBinder; myBinder = theBinder;
} }
/**
* Constructor
*/
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, T theValue) { public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, T theValue) {
myBinder = theBinder; myBinder = theBinder;
setValueAsEnum(theValue); setValueAsEnum(theValue);
} }
/**
* Constructor
*/
public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, Collection<T> theValues) { public BoundCodeableConceptDt(IValueSetEnumBinder<T> theBinder, Collection<T> theValues) {
myBinder = theBinder; myBinder = theBinder;
setValueAsEnum(theValues); setValueAsEnum(theValues);
} }
/**
* Sets the {@link #getCoding()} to contain a coding with the code and
* system defined by the given enumerated types, AND clearing any existing
* codings first. If theValue is null, existing codings are cleared and no
* codings are added.
*
* @param theValue
* The value to add, or <code>null</code>
*/
public void setValueAsEnum(Collection<T> theValues) { public void setValueAsEnum(Collection<T> theValues) {
getCoding().clear(); getCoding().clear();
if (theValues == null) { if (theValues != null) {
return;
}
for (T next : theValues) { for (T next : theValues) {
getCoding().add(new CodingDt(myBinder.toSystemString(next), myBinder.toCodeString(next))); getCoding().add(new CodingDt(myBinder.toSystemString(next), myBinder.toCodeString(next)));
} }
} }
}
/**
* Sets the {@link #getCoding()} to contain a coding with the code and
* system defined by the given enumerated type, AND clearing any existing
* codings first. If theValue is null, existing codings are cleared and no
* codings are added.
*
* @param theValue
* The value to add, or <code>null</code>
*/
public void setValueAsEnum(T theValue) { public void setValueAsEnum(T theValue) {
getCoding().clear(); getCoding().clear();
if (theValue == null) { if (theValue == null) {
@ -68,6 +94,17 @@ public class BoundCodeableConceptDt<T extends Enum<?>> extends CodeableConceptDt
getCoding().add(new CodingDt(myBinder.toSystemString(theValue), myBinder.toCodeString(theValue))); getCoding().add(new CodingDt(myBinder.toSystemString(theValue), myBinder.toCodeString(theValue)));
} }
/**
* Loops through the {@link #getCoding() codings} in this codeable concept
* and returns the first bound enumerated type that matches. <b>Use
* caution</b> using this method, see the return description for more
* information.
*
* @return Returns the bound enumerated type, or <code>null</code> if none
* are found. Note that a null return value doesn't neccesarily
* imply that this Codeable Concept has no codes, only that it has
* no codes that match the enum.
*/
public Set<T> getValueAsEnum() { public Set<T> getValueAsEnum() {
Set<T> retVal = new HashSet<T>(); Set<T> retVal = new HashSet<T>();
for (CodingDt next : getCoding()) { for (CodingDt next : getCoding()) {

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
@ -44,6 +45,11 @@ public abstract class BaseParser implements IParser {
private boolean mySuppressNarratives; private boolean mySuppressNarratives;
@Override
public TagList parseTagList(String theString) {
return parseTagList(new StringReader(theString));
}
@SuppressWarnings("cast") @SuppressWarnings("cast")
@Override @Override
public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) { public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) {
@ -68,6 +74,17 @@ public abstract class BaseParser implements IParser {
return stringWriter.toString(); return stringWriter.toString();
} }
@Override
public String encodeTagListToString(TagList theTagList) {
Writer stringWriter = new StringWriter();
try {
encodeTagListToWriter(theTagList, stringWriter);
} catch (IOException e) {
throw new Error("Encountered IOException during write to string - This should not happen!");
}
return stringWriter.toString();
}
@Override @Override
public String encodeBundleToString(Bundle theBundle) throws DataFormatException { public String encodeBundleToString(Bundle theBundle) throws DataFormatException {
if (theBundle == null) { if (theBundle == null) {

View File

@ -27,6 +27,7 @@ import java.io.Writer;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
public interface IParser { public interface IParser {
@ -36,7 +37,28 @@ public interface IParser {
String encodeResourceToString(IResource theResource) throws DataFormatException; String encodeResourceToString(IResource theResource) throws DataFormatException;
void encodeResourceToWriter(IResource theResource, Writer stringWriter) throws IOException, DataFormatException; void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException, DataFormatException;
/**
* Encodes a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
*
* @param theTagList The tag list to encode. Must not be null.
* @return An encoded tag list
*/
String encodeTagListToString(TagList theTagList);
/**
* Encodes a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
*
* @param theTagList The tag list to encode. Must not be null.
* @param theWriter The writer to encode to
*/
void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException;
<T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader); <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader);
@ -80,6 +102,28 @@ public interface IParser {
IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException; IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException;
/**
* Parses a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
*
* @param theReader
* A reader which will supply a tag list
* @return A parsed tag list
*/
TagList parseTagList(Reader theReader);
/**
* Parses a tag list, as defined in the <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">FHIR
* Specification</a>.
*
* @param theString
* A string containing a tag list
* @return A parsed tag list
*/
TagList parseTagList(String theString);
/** /**
* Sets the "pretty print" flag, meaning that the parser will encode * Sets the "pretty print" flag, meaning that the parser will encode
* resources with human-readable spacing and newlines between elements * resources with human-readable spacing and newlines between elements

View File

@ -45,10 +45,13 @@ import javax.json.JsonValue;
import javax.json.JsonValue.ValueType; import javax.json.JsonValue.ValueType;
import javax.json.stream.JsonGenerator; import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory; import javax.json.stream.JsonGeneratorFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import ch.qos.logback.core.boolex.EventEvaluator;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
@ -61,7 +64,7 @@ import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.BaseBundle; import ca.uhn.fhir.model.api.BaseBundle;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleCategory; import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IElement;
@ -69,6 +72,7 @@ import ca.uhn.fhir.model.api.IIdentifiableElement;
import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
@ -101,6 +105,195 @@ public class JsonParser extends BaseParser implements IParser {
myContext = theContext; myContext = theContext;
} }
@Override
public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException {
JsonGenerator eventWriter = createJsonGenerator(theWriter);
eventWriter.writeStartObject();
eventWriter.write("resourceType", "Bundle");
writeTagWithTextNode(eventWriter, "title", theBundle.getTitle());
writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId());
writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());
writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished());
eventWriter.writeStartArray("link");
writeAtomLink(eventWriter, "self", theBundle.getLinkSelf());
writeAtomLink(eventWriter, "first", theBundle.getLinkFirst());
writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious());
writeAtomLink(eventWriter, "next", theBundle.getLinkNext());
writeAtomLink(eventWriter, "last", theBundle.getLinkLast());
writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase());
eventWriter.writeEnd();
writeOptionalTagWithTextNode(eventWriter, "totalResults", theBundle.getTotalResults());
writeAuthor(theBundle, eventWriter);
eventWriter.writeStartArray("entry");
for (BundleEntry nextEntry : theBundle.getEntries()) {
eventWriter.writeStartObject();
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
eventWriter.writeStartArray("link");
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
eventWriter.writeEnd();
writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
if (nextEntry.getCategories() != null) {
eventWriter.writeStartArray("category");
for (Tag next : nextEntry.getCategories()) {
eventWriter.writeStartObject();
eventWriter.write("term", defaultString(next.getTerm()));
eventWriter.write("label", defaultString(next.getLabel()));
eventWriter.write("scheme", defaultString(next.getScheme()));
eventWriter.writeEnd();
}
eventWriter.writeEnd();
}
writeAuthor(nextEntry, eventWriter);
IResource resource = nextEntry.getResource();
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(resource);
encodeResourceToJsonStreamWriter(resDef, resource, eventWriter, "content");
eventWriter.writeEnd(); // entry object
}
eventWriter.writeEnd(); // entry array
eventWriter.writeEnd();
eventWriter.close();
}
@Override
public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException {
Validate.notNull(theResource, "Resource can not be null");
JsonGenerator eventWriter = createJsonGenerator(theWriter);
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null);
eventWriter.flush();
}
@Override
public void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException {
JsonGenerator eventWriter = createJsonGenerator(theWriter);
eventWriter.writeStartObject();
eventWriter.write("resourceType", TagList.ELEMENT_NAME);
eventWriter.writeStartArray(TagList.ATTR_CATEGORY);
for (Tag next : theTagList) {
eventWriter.writeStartObject();
if (isNotBlank(next.getTerm())) {
eventWriter.write(Tag.ATTR_TERM, next.getTerm());
}
if (isNotBlank(next.getLabel())) {
eventWriter.write(Tag.ATTR_LABEL, next.getLabel());
}
if (isNotBlank(next.getScheme())) {
eventWriter.write(Tag.ATTR_SCHEME, next.getScheme());
}
eventWriter.writeEnd();
}
eventWriter.writeEnd();
eventWriter.writeEnd();
eventWriter.flush();
}
@Override
public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
JsonReader reader = Json.createReader(theReader);
JsonObject object = reader.readObject();
JsonValue resourceTypeObj = object.get("resourceType");
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString();
if (!"Bundle".equals(resourceType)) {
throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'");
}
ParserState<Bundle> state = ParserState.getPreAtomInstance(myContext, theResourceType, true);
state.enteringNewElement(null, "feed");
parseBundleChildren(object, state);
state.endingElement();
Bundle retVal = state.getObject();
return retVal;
}
@Override
public <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) {
JsonReader reader = Json.createReader(theReader);
JsonObject object = reader.readObject();
JsonValue resourceTypeObj = object.get("resourceType");
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString();
RuntimeResourceDefinition def;
if (theResourceType != null) {
def = myContext.getResourceDefinition(theResourceType);
} else {
def = myContext.getResourceDefinition(resourceType);
}
ParserState<? extends IResource> state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true);
state.enteringNewElement(null, def.getName());
parseChildren(object, state);
state.endingElement();
@SuppressWarnings("unchecked")
T retVal = (T) state.getObject();
return retVal;
}
@Override
public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) {
return parseResource(theResourceType, new StringReader(theMessageString));
}
@Override
public TagList parseTagList(Reader theReader) {
JsonReader reader = Json.createReader(theReader);
JsonObject object = reader.readObject();
JsonValue resourceTypeObj = object.get("resourceType");
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString();
ParserState<TagList> state = ParserState.getPreTagListInstance(myContext, true);
state.enteringNewElement(null, resourceType);
parseChildren(object, state);
state.endingElement();
return state.getObject();
}
@Override
public IParser setPrettyPrint(boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
return this;
}
private void addToHeldExtensions(int valueIdx, ArrayList<ArrayList<HeldExtension>> list, RuntimeChildDeclaredExtensionDefinition theDef, IElement theValue) { private void addToHeldExtensions(int valueIdx, ArrayList<ArrayList<HeldExtension>> list, RuntimeChildDeclaredExtensionDefinition theDef, IElement theValue) {
list.ensureCapacity(valueIdx); list.ensureCapacity(valueIdx);
while (list.size() <= valueIdx) { while (list.size() <= valueIdx) {
@ -143,73 +336,7 @@ public class JsonParser extends BaseParser implements IParser {
return eventWriter; return eventWriter;
} }
@Override private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef, String theChildName) throws IOException {
public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException {
JsonGenerator eventWriter = createJsonGenerator(theWriter);
eventWriter.writeStartObject();
eventWriter.write("resourceType", "Bundle");
writeTagWithTextNode(eventWriter, "title", theBundle.getTitle());
writeTagWithTextNode(eventWriter, "id", theBundle.getBundleId());
writeOptionalTagWithTextNode(eventWriter, "updated", theBundle.getUpdated());
writeOptionalTagWithTextNode(eventWriter, "published", theBundle.getPublished());
eventWriter.writeStartArray("link");
writeAtomLink(eventWriter, "self", theBundle.getLinkSelf());
writeAtomLink(eventWriter, "first", theBundle.getLinkFirst());
writeAtomLink(eventWriter, "previous", theBundle.getLinkPrevious());
writeAtomLink(eventWriter, "next", theBundle.getLinkNext());
writeAtomLink(eventWriter, "last", theBundle.getLinkLast());
writeAtomLink(eventWriter, "fhir-base", theBundle.getLinkBase());
eventWriter.writeEnd();
writeOptionalTagWithTextNode(eventWriter, "totalResults", theBundle.getTotalResults());
writeAuthor(theBundle, eventWriter);
eventWriter.writeStartArray("entry");
for (BundleEntry nextEntry : theBundle.getEntries()) {
eventWriter.writeStartObject();
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
writeTagWithTextNode(eventWriter, "id", nextEntry.getId());
eventWriter.writeStartArray("link");
writeAtomLink(eventWriter, "self", nextEntry.getLinkSelf());
eventWriter.writeEnd();
writeOptionalTagWithTextNode(eventWriter, "updated", nextEntry.getUpdated());
writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
if (nextEntry.getCategories() != null) {
eventWriter.writeStartArray("category");
for (BundleCategory next : nextEntry.getCategories()) {
eventWriter.writeStartObject();
eventWriter.write("term", defaultString(next.getTerm()));
eventWriter.write("label", defaultString(next.getLabel()));
eventWriter.write("scheme", defaultString(next.getScheme()));
eventWriter.writeEnd();
}
eventWriter.writeEnd();
}
writeAuthor(nextEntry, eventWriter);
IResource resource = nextEntry.getResource();
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(resource);
encodeResourceToJsonStreamWriter(resDef, resource, eventWriter, "content");
eventWriter.writeEnd(); // entry object
}
eventWriter.writeEnd(); // entry array
eventWriter.writeEnd();
eventWriter.close();
}
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, JsonGenerator theWriter, IElement theValue, BaseRuntimeElementDefinition<?> theChildDef,
String theChildName) throws IOException {
switch (theChildDef.getChildType()) { switch (theChildDef.getChildType()) {
case PRIMITIVE_DATATYPE: { case PRIMITIVE_DATATYPE: {
@ -311,8 +438,7 @@ public class JsonParser extends BaseParser implements IParser {
} }
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, List<? extends BaseRuntimeChildDefinition> theChildren) throws IOException {
List<? extends BaseRuntimeChildDefinition> theChildren) throws IOException {
for (BaseRuntimeChildDefinition nextChild : theChildren) { for (BaseRuntimeChildDefinition nextChild : theChildren) {
if (nextChild instanceof RuntimeChildNarrativeDefinition) { if (nextChild instanceof RuntimeChildNarrativeDefinition) {
INarrativeGenerator gen = myContext.getNarrativeGenerator(); INarrativeGenerator gen = myContext.getNarrativeGenerator();
@ -395,7 +521,8 @@ public class JsonParser extends BaseParser implements IParser {
} }
if (extensions.size() > 0 || modifierExtensions.size() > 0) { if (extensions.size() > 0 || modifierExtensions.size() > 0) {
// Ignore extensions if we're encoding a resource, since they are handled one level up // Ignore extensions if we're encoding a resource, since they
// are handled one level up
if (currentChildName != null) { if (currentChildName != null) {
theEventWriter.writeStartArray('_' + currentChildName); theEventWriter.writeStartArray('_' + currentChildName);
@ -443,8 +570,7 @@ public class JsonParser extends BaseParser implements IParser {
} }
} }
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IResource theResource, IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementCompositeDefinition<?> resDef) throws IOException, DataFormatException {
BaseRuntimeElementCompositeDefinition<?> resDef) throws IOException, DataFormatException {
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions()); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions());
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren()); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getChildren());
} }
@ -472,23 +598,12 @@ public class JsonParser extends BaseParser implements IParser {
theEventWriter.writeEnd(); theEventWriter.writeEnd();
} }
@Override
public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws IOException {
Validate.notNull(theResource, "Resource can not be null");
JsonGenerator eventWriter = createJsonGenerator(theWriter);
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
encodeResourceToJsonStreamWriter(resDef, theResource, eventWriter, null);
eventWriter.flush();
}
/** /**
* This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object called _name): resource extensions, and extension extensions * This is useful only for the two cases where extensions are encoded as
* direct children (e.g. not in some object called _name): resource
* extensions, and extension extensions
*/ */
private void extractAndWriteExtensionsAsDirectChild(IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef, private void extractAndWriteExtensionsAsDirectChild(IElement theElement, JsonGenerator theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef, IResource theResource) throws IOException {
IResource theResource) throws IOException {
List<HeldExtension> extensions = new ArrayList<HeldExtension>(0); List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0); List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
@ -563,31 +678,6 @@ public class JsonParser extends BaseParser implements IParser {
} }
} }
@Override
public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
JsonReader reader = Json.createReader(theReader);
JsonObject object = reader.readObject();
JsonValue resourceTypeObj = object.get("resourceType");
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString();
if (!"Bundle".equals(resourceType)) {
throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'");
}
ParserState<Bundle> state = ParserState.getPreAtomInstance(myContext, theResourceType, true);
state.enteringNewElement(null, "feed");
parseBundleChildren(object, state);
state.endingElement();
Bundle retVal = state.getObject();
return retVal;
}
private void parseBundleChildren(JsonObject theObject, ParserState<?> theState) { private void parseBundleChildren(JsonObject theObject, ParserState<?> theState) {
for (String nextName : theObject.keySet()) { for (String nextName : theObject.keySet()) {
if ("resourceType".equals(nextName)) { if ("resourceType".equals(nextName)) {
@ -653,7 +743,7 @@ public class JsonParser extends BaseParser implements IParser {
} }
if (elementId != null) { if (elementId != null) {
IElement object = theState.getObject(); IElement object = (IElement) theState.getObject();
if (object instanceof IIdentifiableElement) { if (object instanceof IIdentifiableElement) {
((IIdentifiableElement) object).setId(new IdDt(elementId)); ((IIdentifiableElement) object).setId(new IdDt(elementId));
} }
@ -717,7 +807,6 @@ public class JsonParser extends BaseParser implements IParser {
} }
private void parseExtension(ParserState<?> theState, JsonArray array, boolean theIsModifier) { private void parseExtension(ParserState<?> theState, JsonArray array, boolean theIsModifier) {
// TODO: use theIsModifier
for (int i = 0; i < array.size(); i++) { for (int i = 0; i < array.size(); i++) {
JsonObject nextExtObj = array.getJsonObject(i); JsonObject nextExtObj = array.getJsonObject(i);
String url = nextExtObj.getString("url"); String url = nextExtObj.getString("url");
@ -741,46 +830,6 @@ public class JsonParser extends BaseParser implements IParser {
} }
} }
@Override
public <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) {
JsonReader reader = Json.createReader(theReader);
JsonObject object = reader.readObject();
JsonValue resourceTypeObj = object.get("resourceType");
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString();
RuntimeResourceDefinition def;
if (theResourceType != null) {
def = myContext.getResourceDefinition(theResourceType);
} else {
def = myContext.getResourceDefinition(resourceType);
}
ParserState<? extends IResource> state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true);
state.enteringNewElement(null, def.getName());
parseChildren(object, state);
state.endingElement();
@SuppressWarnings("unchecked")
T retVal = (T) state.getObject();
return retVal;
}
@Override
public <T extends IResource> T parseResource(Class<T> theResourceType, String theMessageString) {
return parseResource(theResourceType, new StringReader(theMessageString));
}
@Override
public IParser setPrettyPrint(boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
return this;
}
private void writeAtomLink(JsonGenerator theEventWriter, String theRel, StringDt theLink) { private void writeAtomLink(JsonGenerator theEventWriter, String theRel, StringDt theLink) {
if (isNotBlank(theLink.getValue())) { if (isNotBlank(theLink.getValue())) {
theEventWriter.writeStartObject(); theEventWriter.writeStartObject();
@ -801,8 +850,7 @@ public class JsonParser extends BaseParser implements IParser {
} }
} }
private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions, private void writeExtensionsAsDirectChild(IResource theResource, JsonGenerator theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions) throws IOException {
List<HeldExtension> modifierExtensions) throws IOException {
if (extensions.isEmpty() == false) { if (extensions.isEmpty() == false) {
theEventWriter.writeStartArray("extension"); theEventWriter.writeStartArray("extension");
for (HeldExtension next : extensions) { for (HeldExtension next : extensions) {
@ -871,7 +919,9 @@ public class JsonParser extends BaseParser implements IParser {
if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) { if (def.getChildType() == ChildTypeEnum.RESOURCE_BLOCK) {
extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource); extractAndWriteExtensionsAsDirectChild(myValue, theEventWriter, def, theResDef, theResource);
} else { } else {
// encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, "value" + WordUtils.capitalize(def.getName())); // encodeChildElementToStreamWriter(theResDef, theResource,
// theEventWriter, myValue, def, "value" +
// WordUtils.capitalize(def.getName()));
String childName = myDef.getChildNameByDatatype(myValue.getClass()); String childName = myDef.getChildNameByDatatype(myValue.getClass());
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName); encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName);
} }

View File

@ -46,7 +46,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceReferenceDefinition; import ca.uhn.fhir.context.RuntimeResourceReferenceDefinition;
import ca.uhn.fhir.model.api.BaseBundle; import ca.uhn.fhir.model.api.BaseBundle;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleCategory; import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.ICompositeDatatype; import ca.uhn.fhir.model.api.ICompositeDatatype;
@ -57,13 +57,14 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.IResourceBlock; import ca.uhn.fhir.model.api.IResourceBlock;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
class ParserState<T extends IElement> { class ParserState<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParserState.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParserState.class);
private FhirContext myContext; private FhirContext myContext;
@ -151,6 +152,12 @@ class ParserState<T extends IElement> {
return retVal; return retVal;
} }
public static ParserState<TagList> getPreTagListInstance(FhirContext theContext, boolean theJsonMode) {
ParserState<TagList> retVal = new ParserState<TagList>(theContext, theJsonMode);
retVal.push(retVal.new PreTagListState());
return retVal;
}
public class AtomAuthorState extends BaseState { public class AtomAuthorState extends BaseState {
private BaseBundle myInstance; private BaseBundle myInstance;
@ -189,9 +196,9 @@ class ParserState<T extends IElement> {
private int myCatState = STATE_NONE; private int myCatState = STATE_NONE;
private BundleCategory myInstance; private Tag myInstance;
public AtomCategoryState(BundleCategory theEntry) { public AtomCategoryState(Tag theEntry) {
super(null); super(null);
myInstance = theEntry; myInstance = theEntry;
} }
@ -205,7 +212,10 @@ class ParserState<T extends IElement> {
} else if ("scheme".equals(theName)) { } else if ("scheme".equals(theName)) {
myInstance.setScheme(theValue); myInstance.setScheme(theValue);
} else if ("value".equals(theName)) { } else if ("value".equals(theName)) {
// This is for the JSON parsing, which is weird for Categories.. /*
* 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) { switch (myCatState) {
case STATE_LABEL: case STATE_LABEL:
myInstance.setLabel(theValue); myInstance.setLabel(theValue);
@ -306,12 +316,39 @@ class ParserState<T extends IElement> {
if (myEntry.getUpdated().isEmpty() == false) { if (myEntry.getUpdated().isEmpty() == false) {
metadata.put(ResourceMetadataKeyEnum.UPDATED, myEntry.getUpdated()); metadata.put(ResourceMetadataKeyEnum.UPDATED, myEntry.getUpdated());
} }
if (myEntry.getCategories().isEmpty() == false) {
TagList tagList = new TagList();
for (Tag next : myEntry.getCategories()) {
tagList.add(next);
}
metadata.put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
}
if (!myEntry.getLinkSelf().isEmpty()) { if (!myEntry.getLinkSelf().isEmpty()) {
String subStr = "/" + Constants.PARAM_HISTORY + "/";
String linkSelfValue = myEntry.getLinkSelf().getValue(); String linkSelfValue = myEntry.getLinkSelf().getValue();
int startIndex = linkSelfValue.indexOf(subStr); /*
* Find resource ID if it is there
*/
String resNameLc = myContext.getResourceDefinition(myEntry.getResource()).getName().toLowerCase();
String subStrId = "/" + resNameLc + "/";
int idIdx = linkSelfValue.toLowerCase().lastIndexOf(subStrId);
if (idIdx != -1) {
int endIndex = linkSelfValue.indexOf('/', idIdx + subStrId.length());
String id;
if (endIndex == -1) {
id = linkSelfValue.substring(idIdx + subStrId.length());
} else {
id = linkSelfValue.substring(idIdx + subStrId.length(), endIndex);
}
myEntry.getResource().setId(new IdDt(id));
}
/*
* Find resource version ID if it is there
*/
String subStrVid = "/" + Constants.PARAM_HISTORY + "/";
int startIndex = linkSelfValue.indexOf(subStrVid);
if (startIndex > 0) { if (startIndex > 0) {
startIndex = startIndex + subStr.length(); startIndex = startIndex + subStrVid.length();
int endIndex = linkSelfValue.indexOf('?', startIndex); int endIndex = linkSelfValue.indexOf('?', startIndex);
if (endIndex == -1) { if (endIndex == -1) {
endIndex = linkSelfValue.length(); endIndex = linkSelfValue.length();
@ -324,7 +361,7 @@ class ParserState<T extends IElement> {
ourLog.warn("Bundle entry link-self contains path information beyond version (this will be ignored): {}", versionId); ourLog.warn("Bundle entry link-self contains path information beyond version (this will be ignored): {}", versionId);
versionId = versionId.substring(0, idx); versionId = versionId.substring(0, idx);
} }
metadata.put(ResourceMetadataKeyEnum.VERSION_ID, versionId); metadata.put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt(versionId));
} }
} }
@ -548,7 +585,7 @@ class ParserState<T extends IElement> {
// ignore // ignore
} }
protected IElement getCurrentElement() { protected Object getCurrentElement() {
return null; return null;
} }
@ -993,6 +1030,47 @@ class ParserState<T extends IElement> {
} }
private class PreTagListState extends BaseState {
private TagList myTagList;
public PreTagListState() {
super(null);
myTagList = new TagList();
}
@Override
public void endingElement() throws DataFormatException {
pop();
}
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if (!TagList.ELEMENT_NAME_LC.equals(theLocalPart.toLowerCase())) {
throw new DataFormatException("resourceType does not appear to be 'TagList', found: " + theLocalPart);
}
push(new TagListState(myTagList));
}
@Override
public boolean isPreResource() {
return true;
}
@SuppressWarnings("unchecked")
@Override
public void wereBack() {
myObject = (T) myTagList;
}
@Override
protected TagList getCurrentElement() {
return myTagList;
}
}
private class PrimitiveState extends BaseState { private class PrimitiveState extends BaseState {
private IPrimitiveDatatype<?> myInstance; private IPrimitiveDatatype<?> myInstance;
@ -1137,6 +1215,93 @@ class ParserState<T extends IElement> {
} }
private class TagListState extends BaseState {
private TagList myTagList;
public TagListState(TagList theTagList) {
super(null);
myTagList = theTagList;
}
@Override
public void endingElement() throws DataFormatException {
pop();
}
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if (TagList.ATTR_CATEGORY.equals(theLocalPart)) {
push(new TagState(myTagList.addTag()));
} else {
throw new DataFormatException("Unexpected element: " + theLocalPart);
}
}
}
private class TagState extends BaseState {
private static final int LABEL = 2;
private static final int NONE = 0;
private static final int SCHEME = 3;
private static final int TERM = 1;
private int mySubState = 0;
private Tag myTag;
public TagState(Tag theTag) {
super(null);
myTag = theTag;
}
@Override
public void attributeValue(String theName, String theValue) throws DataFormatException {
String value = defaultIfBlank(theValue, null);
switch (mySubState) {
case TERM:
myTag.setTerm(value);
break;
case LABEL:
myTag.setLabel(value);
break;
case SCHEME:
myTag.setScheme(value);
break;
case NONE:
// This handles JSON encoding, which is a bit weird
enteringNewElement(null, theName);
attributeValue(null, value);
endingElement();
break;
}
}
@Override
public void endingElement() throws DataFormatException {
if (mySubState != NONE) {
mySubState = NONE;
} else {
pop();
}
}
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
if (Tag.ATTR_TERM.equals(theLocalPart)) {
mySubState = TERM;
} else if (Tag.ATTR_SCHEME.equals(theLocalPart)) {
mySubState = SCHEME;
} else if (Tag.ATTR_LABEL.equals(theLocalPart)) {
mySubState = LABEL;
} else {
throw new DataFormatException("Unexpected element: " + theLocalPart);
}
}
}
private class XhtmlState extends BaseState { private class XhtmlState extends BaseState {
private int myDepth; private int myDepth;
private XhtmlDt myDt; private XhtmlDt myDt;

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.parser;
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
@ -56,13 +57,14 @@ import ca.uhn.fhir.context.RuntimeChildNarrativeDefinition;
import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition; import ca.uhn.fhir.context.RuntimeChildUndeclaredExtensionDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleCategory; import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IPrimitiveDatatype; import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.ContainedDt; import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
@ -104,9 +106,7 @@ public class XmlParser extends BaseParser implements IParser {
@Override @Override
public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException { public void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException {
try { try {
XMLStreamWriter eventWriter; XMLStreamWriter eventWriter = createXmlWriter(theWriter);
eventWriter = myXmlOutputFactory.createXMLStreamWriter(theWriter);
eventWriter = decorateStreamWriter(eventWriter);
eventWriter.writeStartElement("feed"); eventWriter.writeStartElement("feed");
eventWriter.writeDefaultNamespace(ATOM_NS); eventWriter.writeDefaultNamespace(ATOM_NS);
@ -147,7 +147,7 @@ public class XmlParser extends BaseParser implements IParser {
writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished()); writeOptionalTagWithTextNode(eventWriter, "published", nextEntry.getPublished());
if (nextEntry.getCategories() != null) { if (nextEntry.getCategories() != null) {
for (BundleCategory next : nextEntry.getCategories()) { for (Tag next : nextEntry.getCategories()) {
eventWriter.writeStartElement("category"); eventWriter.writeStartElement("category");
eventWriter.writeAttribute("term", defaultString(next.getTerm())); eventWriter.writeAttribute("term", defaultString(next.getTerm()));
eventWriter.writeAttribute("label", defaultString(next.getLabel())); eventWriter.writeAttribute("label", defaultString(next.getLabel()));
@ -193,11 +193,10 @@ public class XmlParser extends BaseParser implements IParser {
} }
@Override @Override
public void encodeResourceToWriter(IResource theResource, Writer stringWriter) throws DataFormatException { public void encodeResourceToWriter(IResource theResource, Writer theWriter) throws DataFormatException {
XMLStreamWriter eventWriter; XMLStreamWriter eventWriter;
try { try {
eventWriter = myXmlOutputFactory.createXMLStreamWriter(stringWriter); eventWriter = createXmlWriter(theWriter);
eventWriter = decorateStreamWriter(eventWriter);
encodeResourceToXmlStreamWriter(theResource, eventWriter, false); encodeResourceToXmlStreamWriter(theResource, eventWriter, false);
eventWriter.flush(); eventWriter.flush();
@ -206,22 +205,73 @@ public class XmlParser extends BaseParser implements IParser {
} }
} }
private XMLStreamWriter createXmlWriter(Writer theWriter) throws XMLStreamException {
XMLStreamWriter eventWriter;
eventWriter = myXmlOutputFactory.createXMLStreamWriter(theWriter);
eventWriter = decorateStreamWriter(eventWriter);
return eventWriter;
}
@Override @Override
public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) { public void encodeTagListToWriter(TagList theTagList, Writer theWriter) throws IOException {
XMLEventReader streamReader;
try { try {
streamReader = myXmlInputFactory.createXMLEventReader(theReader); XMLStreamWriter eventWriter = createXmlWriter(theWriter);
eventWriter.writeStartElement(TagList.ELEMENT_NAME_LC);
eventWriter.writeDefaultNamespace(FHIR_NS);
for (Tag next : theTagList) {
eventWriter.writeStartElement(TagList.ATTR_CATEGORY);
if (isNotBlank(next.getTerm())) {
eventWriter.writeAttribute(Tag.ATTR_TERM, next.getTerm());
}
if (isNotBlank(next.getLabel())) {
eventWriter.writeAttribute(Tag.ATTR_LABEL, next.getLabel());
}
if (isNotBlank(next.getScheme())) {
eventWriter.writeAttribute(Tag.ATTR_SCHEME, next.getScheme());
}
eventWriter.writeEndElement();
}
eventWriter.writeEndElement();
eventWriter.close();
} catch (XMLStreamException e) { } catch (XMLStreamException e) {
throw new DataFormatException(e);
} catch (FactoryConfigurationError e) {
throw new ConfigurationException("Failed to initialize STaX event factory", e); throw new ConfigurationException("Failed to initialize STaX event factory", e);
} }
}
@Override
public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
XMLEventReader streamReader = createStreamReader(theReader);
return parseBundle(streamReader, theResourceType); return parseBundle(streamReader, theResourceType);
} }
@Override @Override
public <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) { public <T extends IResource> T parseResource(Class<T> theResourceType, Reader theReader) {
XMLEventReader streamReader = createStreamReader(theReader);
return parseResource(theResourceType, streamReader);
}
@Override
public TagList parseTagList(Reader theReader) {
XMLEventReader streamReader = createStreamReader(theReader);
ParserState<TagList> parserState = ParserState.getPreTagListInstance(myContext, false);
return doXmlLoop(streamReader, parserState);
}
@Override
public IParser setPrettyPrint(boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
return this;
}
private XMLEventReader createStreamReader(Reader theReader) {
XMLEventReader streamReader; XMLEventReader streamReader;
try { try {
streamReader = myXmlInputFactory.createXMLEventReader(theReader); streamReader = myXmlInputFactory.createXMLEventReader(theReader);
@ -230,14 +280,7 @@ public class XmlParser extends BaseParser implements IParser {
} catch (FactoryConfigurationError e) { } catch (FactoryConfigurationError e) {
throw new ConfigurationException("Failed to initialize STaX event factory", e); throw new ConfigurationException("Failed to initialize STaX event factory", e);
} }
return streamReader;
return parseResource(theResourceType, streamReader);
}
@Override
public IParser setPrettyPrint(boolean thePrettyPrint) {
myPrettyPrint = thePrettyPrint;
return this;
} }
private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) { private XMLStreamWriter decorateStreamWriter(XMLStreamWriter eventWriter) {
@ -249,7 +292,9 @@ public class XmlParser extends BaseParser implements IParser {
} }
} }
private <T extends IElement> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) { private <T> T doXmlLoop(XMLEventReader streamReader, ParserState<T> parserState) {
ourLog.trace("Entering XML parsing loop with state: {}", parserState);
try { try {
while (streamReader.hasNext()) { while (streamReader.hasNext()) {
XMLEvent nextEvent = streamReader.nextEvent(); XMLEvent nextEvent = streamReader.nextEvent();
@ -639,14 +684,6 @@ public class XmlParser extends BaseParser implements IParser {
} }
} }
private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
theEventWriter.writeStartElement(theElementName);
if (StringUtils.isNotBlank(theStringDt.getValue())) {
theEventWriter.writeCharacters(theStringDt.getValue());
}
theEventWriter.writeEndElement();
}
private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, IdDt theIdDt) throws XMLStreamException { private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, IdDt theIdDt) throws XMLStreamException {
theEventWriter.writeStartElement(theElementName); theEventWriter.writeStartElement(theElementName);
if (StringUtils.isNotBlank(theIdDt.getValue())) { if (StringUtils.isNotBlank(theIdDt.getValue())) {
@ -654,4 +691,12 @@ public class XmlParser extends BaseParser implements IParser {
} }
theEventWriter.writeEndElement(); theEventWriter.writeEndElement();
} }
private void writeTagWithTextNode(XMLStreamWriter theEventWriter, String theElementName, StringDt theStringDt) throws XMLStreamException {
theEventWriter.writeStartElement(theElementName);
if (StringUtils.isNotBlank(theStringDt.getValue())) {
theEventWriter.writeCharacters(theStringDt.getValue());
}
theEventWriter.writeEndElement();
}
} }

View File

@ -0,0 +1,59 @@
package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt;
/**
* RESTful method annotation to be used for the FHIR <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">Tag
* Operations</a> which have to do with adding tags.
* <ul>
* <li>
* To add tag(s) <b>to the given resource
* instance</b>, this annotation should contain a {@link #type()} attribute
* specifying the resource type, and the method should have a parameter of type
* {@link IdDt} annotated with the {@link IdParam} annotation, as well as
* a parameter of type {@link TagList}. Note that this {@link TagList} parameter
* does not need to contain a complete list of tags for the resource, only a list
* of tags to be added. Server implementations must not remove tags based on this
* operation.
* Note that for a
* server implementation, the {@link #type()} annotation is optional if the
* method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* <li>
* To add tag(s) on the server <b>to the given version of the
* resource instance</b>, this annotation should contain a {@link #type()}
* attribute specifying the resource type, and the method should have a
* parameter of type {@link IdDt} annotated with the {@link VersionIdParam}
* annotation, <b>and</b> a parameter of type {@link IdDt} annotated with the
* {@link IdParam} annotation, as well as
* a parameter of type {@link TagList}. Note that this {@link TagList} parameter
* does not need to contain a complete list of tags for the resource, only a list
* of tags to be added. Server implementations must not remove tags based on this
* operation.
* Note that for a server implementation, the
* {@link #type()} annotation is optional if the method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* </ul>
*/
@Target(value= ElementType.METHOD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface AddTags {
/**
* If set to a type other than the default (which is {@link IResource.class}
* , this method is expected to return a TagList containing only tags which
* are specific to the given resource type.
*/
Class<? extends IResource> type() default IResource.class;
}

View File

@ -0,0 +1,55 @@
package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.primitive.IdDt;
/**
* RESTful method annotation to be used for the FHIR <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">Tag
* Operations</a> which have to do with deleting tags.
* <ul>
* <li>
* To delete tag(s) <b>to the given resource
* instance</b>, this annotation should contain a {@link #type()} attribute
* specifying the resource type, and the method should have a parameter of type
* {@link IdDt} annotated with the {@link IdParam} annotation, as well as
* a parameter of type {@link TagList} which will contain the list of tags
* to be deleted.
* Note that for a
* server implementation, the {@link #type()} annotation is optional if the
* method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* <li>
* To delete tag(s) on the server <b>to the given version of the
* resource instance</b>, this annotation should contain a {@link #type()}
* attribute specifying the resource type, and the method should have a
* parameter of type {@link IdDt} annotated with the {@link VersionIdParam}
* annotation, <b>and</b> a parameter of type {@link IdDt} annotated with the
* {@link IdParam} annotation, as well as
* a parameter of type {@link TagList} which will contain the list of tags
* to be deleted.
* Note that for a server implementation, the
* {@link #type()} annotation is optional if the method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* </ul>
*/
@Target(value= ElementType.METHOD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface DeleteTags {
/**
* If set to a type other than the default (which is {@link IResource.class}
* , this method is expected to return a TagList containing only tags which
* are specific to the given resource type.
*/
Class<? extends IResource> type() default IResource.class;
}

View File

@ -0,0 +1,63 @@
package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
/**
* RESTful method annotation to be used for the FHIR <a
* href="http://hl7.org/implement/standards/fhir/http.html#tags">Tag
* Operations</a> which have to do with getting tags.
* <ul>
* <li>
* To return a global list of all tags on the server, this annotation should not
* contain a {@link #type()} attribute, and the method should not have an ID or
* Version ID parameter. On server implementations, the method must be defined
* in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#plain_providers"
* >plain provider</a>.</li>
* <li>
* To return a list of all tags on the server <b>for the given resource
* type</b>, this annotation should contain a {@link #type()} attribute
* specifying the resource type, and the method should not have an ID or Version
* ID parameter. Note that for a server implementation, the {@link #type()}
* annotation is optional if the method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* <li>
* To return a list of all tags on the server <b>for the given resource
* instance</b>, this annotation should contain a {@link #type()} attribute
* specifying the resource type, and the method should have a parameter of type
* {@link IdDt} annotated with the {@link IdParam} annotation. Note that for a
* server implementation, the {@link #type()} annotation is optional if the
* method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* <li>
* To return a list of all tags on the server <b>for the given version of the
* resource instance</b>, this annotation should contain a {@link #type()}
* attribute specifying the resource type, and the method should have a
* parameter of type {@link IdDt} annotated with the {@link VersionIdParam}
* annotation, <b>and</b> a parameter of type {@link IdDt} annotated with the
* {@link IdParam} annotation. Note that for a server implementation, the
* {@link #type()} annotation is optional if the method is defined in a <a href=
* "http://hl7api.sourceforge.net/hapi-fhir/doc_rest_server.html#resource_providers"
* >resource provider</a>, since the type is implied.</li>
* </ul>
*/
@Target(value= ElementType.METHOD)
@Retention(value=RetentionPolicy.RUNTIME)
public @interface GetTags {
/**
* If set to a type other than the default (which is {@link IResource.class}
* , this method is expected to return a TagList containing only tags which
* are specific to the given resource type.
*/
Class<? extends IResource> type() default IResource.class;
}

View File

@ -112,7 +112,7 @@ public abstract class BaseClient {
HttpRequestBase httpRequest; HttpRequestBase httpRequest;
HttpResponse response; HttpResponse response;
try { try {
httpRequest = clientInvocation.asHttpRequest(myUrlBase, createExtraParams()); httpRequest = clientInvocation.asHttpRequest(myUrlBase, createExtraParams(), getEncoding());
response = myClient.execute(httpRequest); response = myClient.execute(httpRequest);
} catch (DataFormatException e) { } catch (DataFormatException e) {
throw new FhirClientConnectionException(e); throw new FhirClientConnectionException(e);
@ -179,9 +179,9 @@ public abstract class BaseClient {
HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>(); HashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
if (getEncoding() == EncodingEnum.XML) { if (getEncoding() == EncodingEnum.XML) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList(Constants.CT_XML)); retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (getEncoding() == EncodingEnum.JSON) { } else if (getEncoding() == EncodingEnum.JSON) {
retVal.put(Constants.PARAM_FORMAT, Collections.singletonList(Constants.CT_JSON)); retVal.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
} }
if (isPrettyPrint()) { if (isPrettyPrint()) {
@ -229,7 +229,7 @@ public abstract class BaseClient {
return new StringReader(""); return new StringReader("");
} }
Charset charset = null; Charset charset = null;
if (entity.getContentType().getElements() != null) { if (entity.getContentType() != null && entity.getContentType().getElements() != null && entity.getContentType().getElements().length>0) {
ContentType ct = ContentType.get(entity); ContentType ct = ContentType.get(entity);
charset = ct.getCharset(); charset = ct.getCharset();
} }

View File

@ -31,6 +31,8 @@ import org.apache.http.Header;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import ca.uhn.fhir.rest.server.EncodingEnum;
public abstract class BaseClientInvocation { public abstract class BaseClientInvocation {
private List<Header> myHeaders; private List<Header> myHeaders;
@ -49,8 +51,11 @@ public abstract class BaseClientInvocation {
* The FHIR server base url (with a trailing "/") * The FHIR server base url (with a trailing "/")
* @param theExtraParams * @param theExtraParams
* Any extra request parameters the server wishes to add * Any extra request parameters the server wishes to add
* @param theEncoding
* The encoding to use for any serialized content sent to the
* server
*/ */
public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams); public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding);
protected static void appendExtraParamsWithQuestionMark(Map<String, List<String>> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) { protected static void appendExtraParamsWithQuestionMark(Map<String, List<String>> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) {
boolean first = theWithQuestionMark; boolean first = theWithQuestionMark;

View File

@ -20,48 +20,52 @@ package ca.uhn.fhir.rest.client;
* #L% * #L%
*/ */
import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicHeader;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
public abstract class BaseClientInvocationWithContents extends BaseClientInvocation { public abstract class BaseClientInvocationWithContents extends BaseClientInvocation {
private final Bundle myBundle;
private final FhirContext myContext; private final FhirContext myContext;
private final IResource myResource; private final IResource myResource;
private String myUrlExtension; private final String myUrlExtension;
private final TagList myTagList;
public BaseClientInvocationWithContents(FhirContext theContext, Bundle theBundle) {
super();
myContext = theContext;
myResource = null;
myBundle = theBundle;
}
public BaseClientInvocationWithContents(FhirContext theContext, IResource theResource, String theUrlExtension) { public BaseClientInvocationWithContents(FhirContext theContext, IResource theResource, String theUrlExtension) {
super(); super();
myContext = theContext; myContext = theContext;
myResource = theResource; myResource = theResource;
myBundle = null;
myUrlExtension = theUrlExtension; myUrlExtension = theUrlExtension;
myTagList = null;
}
public BaseClientInvocationWithContents(FhirContext theContext, TagList theTagList, String... theUrlExtension) {
super();
if (theTagList == null) {
throw new NullPointerException("Tag list must not be null");
}
myResource = null;
myContext = theContext;
myTagList = theTagList;
myUrlExtension = StringUtils.join(theUrlExtension, '/');
} }
@Override @Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams) throws DataFormatException { public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) throws DataFormatException {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(theUrlBase); b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) { if (!theUrlBase.endsWith("/")) {
@ -71,11 +75,26 @@ public abstract class BaseClientInvocationWithContents extends BaseClientInvocat
appendExtraParamsWithQuestionMark(theExtraParams, b, true); appendExtraParamsWithQuestionMark(theExtraParams, b, true);
String url = b.toString(); IParser parser;
String contents = myContext.newXmlParser().encodeResourceToString(myResource); String contentType;
StringEntity entity = new StringEntity(contents, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")); if (theEncoding == EncodingEnum.JSON) {
parser = myContext.newJsonParser();
contentType = Constants.CT_FHIR_JSON;
} else {
parser = myContext.newXmlParser();
contentType = Constants.CT_FHIR_XML;
}
HttpRequestBase retVal = createRequest(url, entity); String contents;
if (myTagList != null) {
contents = parser.encodeTagListToString(myTagList);
} else {
contents = parser.encodeResourceToString(myResource);
}
StringEntity entity = new StringEntity(contents, ContentType.create(contentType, "UTF-8"));
HttpRequestBase retVal = createRequest(b.toString(), entity);
super.addHeadersToRequest(retVal); super.addHeadersToRequest(retVal);
return retVal; return retVal;
} }

View File

@ -1,124 +0,0 @@
package ca.uhn.fhir.rest.client;
/*
* #%L
* HAPI FHIR Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* 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%
*/
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.primitive.Base64BinaryDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
public class ClientCache {
private FhirContext myPrivateContext;
private FhirContext myPublicContext;
private ClientCache(FhirContext theContext) {
myPublicContext = theContext;
myPrivateContext = new FhirContext(CacheEntry.class);
}
/**
* Accepts a restful client instance and decorates it
*/
public <T extends IRestfulClient> T decorateClient(Class<T> theInterface, T theClientToDecorate) {
for (Method nextMethod : theInterface.getMethods()){
}
return theClientToDecorate;
}
@ResourceDef(name="HapiClientCacheEntry", id="hapiclientcacheentry")
private static class CacheEntry {
@Child(order=2, min=0,max=Child.MAX_UNLIMITED,name="arguments")
private List<IDatatype> myArguments;
@Child(order=1, min=0,max=Child.MAX_UNLIMITED,name="argumentTypes")
private List<StringDt> myArgumentTypes;
@Child(order=3, min=1,max=1,name="loaded")
private InstantDt myLoaded;
@Child(order=0, min=1, max=1, name="methodName")
private StringDt myMethodName;
@Child(order=4, min=1,max=1,name="resourceText")
private Base64BinaryDt myResourceText;
public List<IDatatype> getArguments() {
if (myArguments==null) {
myArguments=new ArrayList<IDatatype>();
}
return myArguments;
}
public List<StringDt> getArgumentTypes() {
return myArgumentTypes;
}
public InstantDt getLoaded() {
return myLoaded;
}
public StringDt getMethodName() {
return myMethodName;
}
public Base64BinaryDt getResourceText() {
return myResourceText;
}
public void setArguments(List<IDatatype> theArguments) {
myArguments = theArguments;
}
public void setArgumentTypes(List<StringDt> theArgumentTypes) {
myArgumentTypes = theArgumentTypes;
}
public void setLoaded(InstantDt theLoaded) {
myLoaded = theLoaded;
}
public void setMethodName(StringDt theMethodName) {
myMethodName = theMethodName;
}
public void setResourceText(Base64BinaryDt theResourceText) {
myResourceText = theResourceText;
}
}
}

View File

@ -31,10 +31,14 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class ClientInvocationHandler extends BaseClient implements InvocationHandler { public class ClientInvocationHandler extends BaseClient implements InvocationHandler {
private final Map<Method, BaseMethodBinding> myBindings = new HashMap<Method, BaseMethodBinding>(); private final Map<Method, BaseMethodBinding> myBindings = new HashMap<Method, BaseMethodBinding>();
private final Map<Method, ILambda> myMethodToLambda = new HashMap<Method, ILambda>();
private final Map<Method, Object> myMethodToReturnValue = new HashMap<Method, Object>(); private final Map<Method, Object> myMethodToReturnValue = new HashMap<Method, Object>();
public ClientInvocationHandler(HttpClient theClient, FhirContext theContext, String theUrlBase, Class<? extends IRestfulClient> theClientType) { public ClientInvocationHandler(HttpClient theClient, FhirContext theContext, String theUrlBase, Class<? extends IRestfulClient> theClientType) {
@ -44,6 +48,10 @@ public class ClientInvocationHandler extends BaseClient implements InvocationHan
myMethodToReturnValue.put(theClientType.getMethod("getFhirContext"), theContext); myMethodToReturnValue.put(theClientType.getMethod("getFhirContext"), theContext);
myMethodToReturnValue.put(theClientType.getMethod("getHttpClient"), theClient); myMethodToReturnValue.put(theClientType.getMethod("getHttpClient"), theClient);
myMethodToReturnValue.put(theClientType.getMethod("getServerBase"), theUrlBase); myMethodToReturnValue.put(theClientType.getMethod("getServerBase"), theUrlBase);
myMethodToLambda.put(theClientType.getMethod("setEncoding", EncodingEnum.class), new SetEncodingLambda());
myMethodToLambda.put(theClientType.getMethod("setPrettyPrint", boolean.class), new SetPrettyPrintLambda());
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new ConfigurationException("Failed to find methods on client. This is a HAPI bug!", e); throw new ConfigurationException("Failed to find methods on client. This is a HAPI bug!", e);
} catch (SecurityException e) { } catch (SecurityException e) {
@ -63,11 +71,39 @@ public class ClientInvocationHandler extends BaseClient implements InvocationHan
} }
BaseMethodBinding binding = myBindings.get(theMethod); BaseMethodBinding binding = myBindings.get(theMethod);
if (binding != null) {
BaseClientInvocation clientInvocation = binding.invokeClient(theArgs); BaseClientInvocation clientInvocation = binding.invokeClient(theArgs);
return invokeClient(binding, clientInvocation); return invokeClient(binding, clientInvocation);
} }
ILambda lambda = myMethodToLambda.get(theMethod);
if (lambda != null) {
return lambda.handle(theArgs);
}
throw new UnsupportedOperationException("The method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getSimpleName() + " has no handler. Did you forget to annotate it with a RESTful method annotation?");
}
private interface ILambda {
Object handle(Object[] theArgs);
}
private class SetEncodingLambda implements ILambda {
@Override
public Object handle(Object[] theArgs) {
EncodingEnum encoding = (EncodingEnum) theArgs[0];
setEncoding(encoding);
return null;
}
}
private class SetPrettyPrintLambda implements ILambda {
@Override
public Object handle(Object[] theArgs) {
Boolean prettyPrint = (Boolean) theArgs[0];
setPrettyPrint(prettyPrint);
return null;
}
}
} }

View File

@ -27,6 +27,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class DeleteClientInvocation extends BaseClientInvocation { public class DeleteClientInvocation extends BaseClientInvocation {
private String myUrlPath; private String myUrlPath;
@ -37,7 +39,7 @@ public class DeleteClientInvocation extends BaseClientInvocation {
} }
@Override @Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams) { public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(theUrlBase); b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) { if (!theUrlBase.endsWith("/")) {

View File

@ -75,7 +75,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public <T extends IResource> T read(final Class<T> theType, IdDt theId) { public <T extends IResource> T read(final Class<T> theType, IdDt theId) {
GetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType)); GetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
IClientResponseHandler binding = new IClientResponseHandler() { IClientResponseHandler binding = new IClientResponseHandler() {
@ -92,12 +92,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp; return resp;
} }
@Override @Override
public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) { public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) {
DeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(toResourceName(theType), theId); DeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(toResourceName(theType), theId);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
final String resourceName = myContext.getResourceDefinition(theType).getName(); final String resourceName = myContext.getResourceDefinition(theType).getName();
@ -129,7 +128,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public <T extends IResource> T vread(final Class<T> theType, IdDt theId, IdDt theVersionId) { public <T extends IResource> T vread(final Class<T> theType, IdDt theId, IdDt theVersionId) {
GetClientInvocation invocation = ReadMethodBinding.createVReadInvocation(theId, theVersionId, toResourceName(theType)); GetClientInvocation invocation = ReadMethodBinding.createVReadInvocation(theId, theVersionId, toResourceName(theType));
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
IClientResponseHandler binding = new IClientResponseHandler() { IClientResponseHandler binding = new IClientResponseHandler() {
@ -159,7 +158,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
GetClientInvocation invocation = SearchMethodBinding.createSearchInvocation(toResourceName(theType), params); GetClientInvocation invocation = SearchMethodBinding.createSearchInvocation(toResourceName(theType), params);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
IClientResponseHandler binding = new IClientResponseHandler() { IClientResponseHandler binding = new IClientResponseHandler() {
@ -179,7 +178,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome create(IResource theResource) { public MethodOutcome create(IResource theResource) {
BaseClientInvocation invocation = CreateMethodBinding.createCreateInvocation(theResource, myContext); BaseClientInvocation invocation = CreateMethodBinding.createCreateInvocation(theResource, myContext);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
@ -202,7 +201,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome update(IdDt theIdDt, IResource theResource) { public MethodOutcome update(IdDt theIdDt, IResource theResource) {
BaseClientInvocation invocation = UpdateMethodBinding.createUpdateInvocation(theResource, theIdDt, null, myContext); BaseClientInvocation invocation = UpdateMethodBinding.createUpdateInvocation(theResource, theIdDt, null, myContext);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
@ -224,7 +223,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public MethodOutcome validate(IResource theResource) { public MethodOutcome validate(IResource theResource) {
BaseClientInvocation invocation = ValidateMethodBinding.createValidateInvocation(theResource, null, myContext); BaseClientInvocation invocation = ValidateMethodBinding.createValidateInvocation(theResource, null, myContext);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
@ -246,7 +245,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt) { public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt) {
GetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(toResourceName(theType), theIdDt); GetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(toResourceName(theType), theIdDt);
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
IClientResponseHandler binding = new IClientResponseHandler() { IClientResponseHandler binding = new IClientResponseHandler() {
@ -267,7 +266,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public Conformance conformance() { public Conformance conformance() {
GetClientInvocation invocation = ConformanceMethodBinding.createConformanceInvocation(); GetClientInvocation invocation = ConformanceMethodBinding.createConformanceInvocation();
if (isKeepResponses()) { if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams()); myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
} }
IClientResponseHandler binding = new IClientResponseHandler() { IClientResponseHandler binding = new IClientResponseHandler() {

View File

@ -32,6 +32,8 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class GetClientInvocation extends BaseClientInvocation { public class GetClientInvocation extends BaseClientInvocation {
@ -48,7 +50,6 @@ public class GetClientInvocation extends BaseClientInvocation {
myUrlPath = theUrlPath; myUrlPath = theUrlPath;
} }
public GetClientInvocation(String... theUrlFragments) { public GetClientInvocation(String... theUrlFragments) {
myParameters = new HashMap<String, List<String>>(); myParameters = new HashMap<String, List<String>>();
myUrlPath = StringUtils.join(theUrlFragments, '/'); myUrlPath = StringUtils.join(theUrlFragments, '/');
@ -63,7 +64,7 @@ public class GetClientInvocation extends BaseClientInvocation {
} }
@Override @Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams) { public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding) {
StringBuilder b = new StringBuilder(); StringBuilder b = new StringBuilder();
b.append(theUrlBase); b.append(theUrlBase);
if (!theUrlBase.endsWith("/")) { if (!theUrlBase.endsWith("/")) {
@ -76,20 +77,9 @@ public class GetClientInvocation extends BaseClientInvocation {
if (next.getValue() == null || next.getValue().isEmpty()) { if (next.getValue() == null || next.getValue().isEmpty()) {
continue; continue;
} }
String nextKey = next.getKey();
for (String nextValue : next.getValue()) { for (String nextValue : next.getValue()) {
if (first) { first = addQueryParameter(b, first, nextKey, nextValue);
b.append('?');
first = false;
} else {
b.append('&');
}
try {
b.append(URLEncoder.encode(next.getKey(), "UTF-8"));
b.append('=');
b.append(URLEncoder.encode(nextValue, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new ConfigurationException("Could not find UTF-8 encoding. This shouldn't happen.", e);
}
} }
} }
@ -101,4 +91,21 @@ public class GetClientInvocation extends BaseClientInvocation {
return retVal; return retVal;
} }
private boolean addQueryParameter(StringBuilder b, boolean first, String nextKey, String nextValue) {
if (first) {
b.append('?');
first = false;
} else {
b.append('&');
}
try {
b.append(URLEncoder.encode(nextKey, "UTF-8"));
b.append('=');
b.append(URLEncoder.encode(nextValue, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new ConfigurationException("Could not find UTF-8 encoding. This shouldn't happen.", e);
}
return first;
}
} }

View File

@ -24,19 +24,21 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
public class PostClientInvocation extends BaseClientInvocationWithContents { public class PostClientInvocation extends BaseClientInvocationWithContents {
public PostClientInvocation(FhirContext theContext, Bundle theBundle) {
super(theContext, theBundle);
}
public PostClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) { public PostClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) {
super(theContext, theResource, theUrlExtension); super(theContext, theResource, theUrlExtension);
} }
public PostClientInvocation(FhirContext theContext, TagList theTagList, String... theUrlExtension) {
super(theContext, theTagList, theUrlExtension);
}
@Override @Override
protected HttpPost createRequest(String url, StringEntity theEntity) { protected HttpPost createRequest(String url, StringEntity theEntity) {
HttpPost retVal = new HttpPost(url); HttpPost retVal = new HttpPost(url);

View File

@ -25,15 +25,10 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
public class PutClientInvocation extends BaseClientInvocationWithContents { public class PutClientInvocation extends BaseClientInvocationWithContents {
public PutClientInvocation(FhirContext theContext, Bundle theBundle) {
super(theContext, theBundle);
}
public PutClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) { public PutClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) {
super(theContext, theResource, theUrlExtension); super(theContext, theResource, theUrlExtension);
} }

View File

@ -28,8 +28,15 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
public interface IRestfulClient { public interface IRestfulClient {
/**
* Returns the FHIR context associated with this client
*/
FhirContext getFhirContext(); FhirContext getFhirContext();
/**
* Do not call this method in client code. It is a part of the internal HAPI API and
* is subject to change!
*/
HttpClient getHttpClient(); HttpClient getHttpClient();
/** /**
@ -37,6 +44,9 @@ public interface IRestfulClient {
* queries. This means that the client will append the "_format" param * queries. This means that the client will append the "_format" param
* to GET methods (read/search/etc), and will add an appropriate header for * to GET methods (read/search/etc), and will add an appropriate header for
* write methods. * write methods.
*
* @param theEncoding The encoding to use in the request, or <code>null</code> not specify
* an encoding (which generally implies the use of XML). The default is <code>null</code>.
*/ */
void setEncoding(EncodingEnum theEncoding); void setEncoding(EncodingEnum theEncoding);
@ -44,6 +54,8 @@ public interface IRestfulClient {
* Specifies that the client should request that the server respond with "pretty printing" * Specifies that the client should request that the server respond with "pretty printing"
* enabled. Note that this is a non-standard parameter, so it may only * enabled. Note that this is a non-standard parameter, so it may only
* work against HAPI based servers. * work against HAPI based servers.
*
* @param thePrettyPrint The pretty print flag to use in the request (default is <code>false</code>)
*/ */
void setPrettyPrint(boolean thePrettyPrint); void setPrettyPrint(boolean thePrettyPrint);

View File

@ -25,8 +25,6 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public class NonFhirResponseException extends BaseServerResponseException { public class NonFhirResponseException extends BaseServerResponseException {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final String myContentType;
private final String myResponseText;
/** /**
* Constructor * Constructor
@ -37,18 +35,8 @@ public class NonFhirResponseException extends BaseServerResponseException {
* @param theStatusCode * @param theStatusCode
* @param theContentType * @param theContentType
*/ */
public NonFhirResponseException(String theMessage, String theContentType, int theStatusCode, String theResponseText) { public NonFhirResponseException(int theStatusCode, String theMessage) {
super(theStatusCode, theMessage); super(theStatusCode, theMessage);
myContentType = theContentType;
myResponseText = theResponseText;
}
public String getContentType() {
return myContentType;
}
public String getResponseText() {
return myResponseText;
} }
} }

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.rest.method;
import java.lang.reflect.Method;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.AddTags;
public class AddTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
public AddTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, AddTags theAnnotation) {
super(theMethod, theConetxt, theProvider, theAnnotation.type());
}
@Override
protected boolean isDelete() {
return false;
}
}

View File

@ -0,0 +1,207 @@
package ca.uhn.fhir.rest.method;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.TagListParam;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.PostClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding {
private Class<? extends IResource> myType;
private Integer myIdParamIndex;
private Integer myVersionIdParamIndex;
private String myResourceName;
private Integer myTagListParamIndex;
public BaseAddOrDeleteTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, Class<? extends IResource> theTypeFromMethodAnnotation) {
super(theMethod, theConetxt, theProvider);
if (theProvider instanceof IResourceProvider) {
myType = ((IResourceProvider) theProvider).getResourceType();
} else {
myType = theTypeFromMethodAnnotation;
}
if (myType.equals(IResource.class)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not specify a resource type, but has an @" + IdParam.class.getSimpleName() + " parameter. Please specity a resource type in the method annotation on this method");
}
myResourceName = theConetxt.getResourceDefinition(myType).getName();
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod);
myVersionIdParamIndex = ParameterUtil.findVersionIdParameterIndex(theMethod);
myTagListParamIndex = ParameterUtil.findTagListParameterIndex(theMethod);
if (myIdParamIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not have an @" + IdParam.class.getSimpleName() + " parameter.");
}
if (myTagListParamIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not have a parameter of type " + TagList.class.getSimpleName() + ", or paramater is not annotated with the @" + TagListParam.class.getSimpleName() + " annotation");
}
}
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
switch (theResponseStatusCode) {
case Constants.STATUS_HTTP_200_OK:
case Constants.STATUS_HTTP_201_CREATED:
case Constants.STATUS_HTTP_204_NO_CONTENT:
return null;
default:
throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseReader);
}
}
@Override
public String getResourceName() {
return myResourceName;
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
return null;
}
@Override
public RestfulOperationSystemEnum getSystemOperationType() {
return null;
}
protected abstract boolean isDelete();
@Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
PostClientInvocation retVal;
IdDt id = (IdDt) theArgs[myIdParamIndex];
if (id == null || id.isEmpty()) {
throw new InvalidRequestException("ID must not be null or empty for this operation");
}
IdDt versionId = null;
if (myVersionIdParamIndex != null) {
versionId = (IdDt) theArgs[myVersionIdParamIndex];
}
TagList tagList = (TagList) theArgs[myTagListParamIndex];
Class<? extends IResource> type = myType;
assert type != null;
if (isDelete()) {
if (versionId != null) {
retVal = new PostClientInvocation(getContext(), tagList, getResourceName(), id.getValue(), Constants.PARAM_HISTORY, versionId.getValue(), Constants.PARAM_TAGS, Constants.PARAM_DELETE);
} else {
retVal = new PostClientInvocation(getContext(), tagList, getResourceName(), id.getValue(), Constants.PARAM_TAGS, Constants.PARAM_DELETE);
}
} else {
if (versionId != null) {
retVal = new PostClientInvocation(getContext(), tagList, getResourceName(), id.getValue(), Constants.PARAM_HISTORY, versionId.getValue(), Constants.PARAM_TAGS);
} else {
retVal = new PostClientInvocation(getContext(), tagList, getResourceName(), id.getValue(), Constants.PARAM_TAGS);
}
}
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], null, retVal);
}
return retVal;
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
Object[] params = createParametersForServerRequest(theRequest, null);
params[myIdParamIndex] = theRequest.getId();
if (myVersionIdParamIndex != null) {
params[myVersionIdParamIndex] = theRequest.getVersionId();
}
IParser parser = createAppropriateParserForParsingServerRequest(theRequest);
Reader reader = theRequest.getInputReader();
try {
TagList tagList = parser.parseTagList(reader);
params[myTagListParamIndex] = tagList;
} finally {
reader.close();
}
invokeServerMethod(params);
EncodingEnum responseEncoding = determineResponseEncoding(theRequest);
theResponse.setContentType(responseEncoding.getResourceContentType());
theResponse.setStatus(Constants.STATUS_HTTP_200_OK);
theResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theResponse);
PrintWriter writer = theResponse.getWriter();
writer.close();
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (theRequest.getRequestType() != RequestType.POST) {
return false;
}
if (!Constants.PARAM_TAGS.equals(theRequest.getOperation())) {
return false;
}
if (!myResourceName.equals(theRequest.getResourceName())) {
return false;
}
if (theRequest.getId() == null) {
return false;
}
if ((myVersionIdParamIndex != null) != (theRequest.getVersionId() != null)) {
return false;
}
if (isDelete()) {
if (Constants.PARAM_DELETE.equals(theRequest.getSecondaryOperation()) == false) {
return false;
}
} else {
if (theRequest.getSecondaryOperation() != null) {
return false;
}
}
return true;
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.method;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -32,7 +34,6 @@ import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -41,11 +42,16 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete; import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
@ -63,6 +69,13 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionNotSpecifiedException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.ReflectionUtil;
public abstract class BaseMethodBinding implements IClientResponseHandler { public abstract class BaseMethodBinding implements IClientResponseHandler {
@ -72,12 +85,12 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
private List<IParameter> myParameters; private List<IParameter> myParameters;
private Object myProvider; private Object myProvider;
public BaseMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
assert theMethod != null; assert theMethod != null;
assert theConetxt != null; assert theContext != null;
myMethod = theMethod; myMethod = theMethod;
myContext = theConetxt; myContext = theContext;
myProvider = theProvider; myProvider = theProvider;
myParameters = ParameterUtil.getResourceParameters(theMethod); myParameters = ParameterUtil.getResourceParameters(theMethod);
} }
@ -108,25 +121,17 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException; public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
public abstract boolean matches(Request theRequest); public abstract boolean incomingServerRequestMatchesMethod(Request theRequest);
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException { protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) {
IParser parser; EncodingEnum encoding = EncodingEnum.forContentType(theResponseMimeType);
if (Constants.CT_ATOM_XML.equals(theResponseMimeType)) { if (encoding==null) {
parser = getContext().newXmlParser(); NonFhirResponseException ex = new NonFhirResponseException(theResponseStatusCode, "Response contains non-FHIR content-type: " + theResponseMimeType);
} else if (Constants.CT_FHIR_XML.equals(theResponseMimeType)) { populateException(ex, theResponseReader);
parser = getContext().newXmlParser(); throw ex;
} else if (Constants.CT_FHIR_JSON.equals(theResponseMimeType)) {
parser = getContext().newJsonParser(); // TODO: move all this so it only happens in one place in the lib, and maybe use a hashmap?
} else if ("application/json".equals(theResponseMimeType)) {
parser = getContext().newJsonParser();
} else if ("application/xml".equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else if ("text/xml".equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else {
throw new NonFhirResponseException("Response contains non-FHIR content-type: " + theResponseMimeType, theResponseMimeType, theResponseStatusCode, IOUtils.toString(theResponseReader));
} }
IParser parser=encoding.newParser(getContext());
return parser; return parser;
} }
@ -134,10 +139,10 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
return myParameters; return myParameters;
} }
protected Object invokeServerMethod(Object theResourceProvider, Object[] theMethodParams) { protected Object invokeServerMethod(Object[] theMethodParams) {
try { try {
Method method = getMethod(); Method method = getMethod();
return method.invoke(theResourceProvider, theMethodParams); return method.invoke(getProvider(), theMethodParams);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
if (e.getCause() instanceof BaseServerResponseException) { if (e.getCause() instanceof BaseServerResponseException) {
throw (BaseServerResponseException) e.getCause(); throw (BaseServerResponseException) e.getCause();
@ -164,8 +169,11 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
Delete delete = theMethod.getAnnotation(Delete.class); Delete delete = theMethod.getAnnotation(Delete.class);
History history = theMethod.getAnnotation(History.class); History history = theMethod.getAnnotation(History.class);
Validate validate = theMethod.getAnnotation(Validate.class); Validate validate = theMethod.getAnnotation(Validate.class);
GetTags getTags = theMethod.getAnnotation(GetTags.class);
AddTags addTags = theMethod.getAnnotation(AddTags.class);
DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class);
// ** if you add another annotation above, also add it to the next line: // ** if you add another annotation above, also add it to the next line:
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate)) { if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags)) {
return null; return null;
} }
@ -180,7 +188,11 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
} }
Class<?> returnTypeFromMethod = theMethod.getReturnType(); Class<?> returnTypeFromMethod = theMethod.getReturnType();
if (MethodOutcome.class.equals(returnTypeFromMethod)) { if (getTags != null) {
if (!TagList.class.equals(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @" + GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName());
}
} else if (MethodOutcome.class.equals(returnTypeFromMethod)) {
// returns a method outcome // returns a method outcome
} else if (Bundle.class.equals(returnTypeFromMethod)) { } else if (Bundle.class.equals(returnTypeFromMethod)) {
// returns a bundle // returns a bundle
@ -190,7 +202,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod); returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !IResource.class.equals(returnTypeFromMethod)) { if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !IResource.class.equals(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod) throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
+ " - Must return a resource type or a collection (List, Set) of a resource type"); + " - Must return a resource type or a collection (List, Set) with a resource type parameter (e.g. List<Patient> )");
} }
} else { } else {
if (!IResource.class.equals(returnTypeFromMethod) && !verifyIsValidResourceReturnType(returnTypeFromMethod)) { if (!IResource.class.equals(returnTypeFromMethod) && !verifyIsValidResourceReturnType(returnTypeFromMethod)) {
@ -214,6 +226,12 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
returnTypeFromAnnotation = update.type(); returnTypeFromAnnotation = update.type();
} else if (validate != null) { } else if (validate != null) {
returnTypeFromAnnotation = validate.type(); returnTypeFromAnnotation = validate.type();
} else if (getTags != null) {
returnTypeFromAnnotation = getTags.type();
} else if (addTags != null) {
returnTypeFromAnnotation = addTags.type();
} else if (deleteTags != null) {
returnTypeFromAnnotation = deleteTags.type();
} }
if (returnTypeFromRp != null) { if (returnTypeFromRp != null) {
@ -259,6 +277,12 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
return new HistoryMethodBinding(theMethod, theContext, theProvider); return new HistoryMethodBinding(theMethod, theContext, theProvider);
} else if (validate != null) { } else if (validate != null) {
return new ValidateMethodBinding(theMethod, theContext, theProvider); return new ValidateMethodBinding(theMethod, theContext, theProvider);
} else if (getTags != null) {
return new GetTagsMethodBinding(theMethod, theContext, theProvider, getTags);
} else if (addTags != null) {
return new AddTagsMethodBinding(theMethod, theContext, theProvider, addTags);
} else if (deleteTags != null) {
return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
} else { } else {
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
} }
@ -285,8 +309,8 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
// return sm; // return sm;
} }
public static EncodingEnum determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) { public static EncodingEnum determineResponseEncoding(Request theReq) {
String[] format = theParams.remove(Constants.PARAM_FORMAT); String[] format = theReq.getParameters().remove(Constants.PARAM_FORMAT);
if (format != null) { if (format != null) {
for (String nextFormat : format) { for (String nextFormat : format) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat); EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
@ -296,7 +320,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
} }
} }
Enumeration<String> acceptValues = theRequest.getHeaders("Accept"); Enumeration<String> acceptValues = theReq.getServletRequest().getHeaders("Accept");
if (acceptValues != null) { if (acceptValues != null) {
while (acceptValues.hasMoreElements()) { while (acceptValues.hasMoreElements()) {
EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement()); EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement());
@ -308,6 +332,27 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
return EncodingEnum.XML; return EncodingEnum.XML;
} }
protected IParser createAppropriateParserForParsingServerRequest(Request theRequest) {
String contentTypeHeader = theRequest.getServletRequest().getHeader("content-type");
EncodingEnum encoding;
if (isBlank(contentTypeHeader)) {
encoding = EncodingEnum.XML;
} else {
int semicolon = contentTypeHeader.indexOf(';');
if (semicolon!=-1) {
contentTypeHeader=contentTypeHeader.substring(0,semicolon);
}
encoding = EncodingEnum.forContentType(contentTypeHeader);
}
if (encoding==null) {
throw new InvalidRequestException("Request contins non-FHIR conent-type header value: " + contentTypeHeader);
}
IParser parser=encoding.newParser(getContext());
return parser;
}
public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) { public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
Object obj1 = null; Object obj1 = null;
for (Object object : theAnnotations) { for (Object object : theAnnotations) {
@ -349,6 +394,18 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
return retVal; return retVal;
} }
protected Object[] createParametersForServerRequest(Request theRequest, IResource theResource) {
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
if (param == null) {
continue;
}
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, theResource);
}
return params;
}
protected static List<IResource> toResourceList(Object response) throws InternalErrorException { protected static List<IResource> toResourceList(Object response) throws InternalErrorException {
if (response == null) { if (response == null) {
return Collections.emptyList(); return Collections.emptyList();
@ -377,4 +434,47 @@ public abstract class BaseMethodBinding implements IClientResponseHandler {
return prettyPrint; return prettyPrint;
} }
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class);
protected BaseServerResponseException processNon2xxResponseAndReturnExceptionToThrow(int theStatusCode, String theResponseMimeType, Reader theResponseReader) {
BaseServerResponseException ex;
switch (theStatusCode) {
case Constants.STATUS_HTTP_400_BAD_REQUEST:
ex = new InvalidRequestException("Server responded with HTTP 400");
break;
case Constants.STATUS_HTTP_404_NOT_FOUND:
ex = new ResourceNotFoundException("Server responded with HTTP 404");
break;
case Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED:
ex = new MethodNotAllowedException("Server responded with HTTP 405");
break;
case Constants.STATUS_HTTP_409_CONFLICT:
ex = new ResourceVersionConflictException("Server responded with HTTP 409");
break;
case Constants.STATUS_HTTP_412_PRECONDITION_FAILED:
ex = new ResourceVersionNotSpecifiedException("Server responded with HTTP 412");
break;
case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY:
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theStatusCode);
OperationOutcome operationOutcome = parser.parseResource(OperationOutcome.class, theResponseReader);
ex = new UnprocessableEntityException(operationOutcome);
break;
default:
ex = new UnclassifiedServerFailureException(theStatusCode, "Server responded with HTTP " + theStatusCode);
break;
}
populateException(ex, theResponseReader);
return ex;
}
private static void populateException(BaseServerResponseException theEx, Reader theResponseReader) {
try {
String responseText = IOUtils.toString(theResponseReader);
theEx.setResponseBody(responseText);
} catch (IOException e) {
ourLog.debug("Failed to read response", e);
}
}
} }

View File

@ -20,23 +20,28 @@ package ca.uhn.fhir.rest.method;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException; import java.io.IOException;
import java.io.PushbackReader; import java.io.PushbackReader;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
@ -44,22 +49,17 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseClientInvocation; import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionNotSpecifiedException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding { public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
private static final String LABEL = "label=\"";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
private static final String SCHEME = "scheme=\"";
private boolean myReturnVoid; private boolean myReturnVoid;
public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) { public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
@ -74,6 +74,25 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
} }
} }
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
Set<RequestType> allowableRequestTypes = provideAllowableRequestTypes();
RequestType requestType = theRequest.getRequestType();
if (!allowableRequestTypes.contains(requestType)) {
return false;
}
if (!getResourceName().equals(theRequest.getResourceName())) {
return false;
}
if (getMatchingOperation() == null && StringUtils.isNotBlank(theRequest.getOperation())) {
return false;
}
if (getMatchingOperation() != null && !getMatchingOperation().equals(theRequest.getOperation())) {
return false;
}
return true;
}
@Override @Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException { public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
switch (theResponseStatusCode) { switch (theResponseStatusCode) {
@ -85,51 +104,38 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
} }
MethodOutcome retVal = process2xxResponse(getContext(), getResourceName(), theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders); MethodOutcome retVal = process2xxResponse(getContext(), getResourceName(), theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return retVal; return retVal;
case Constants.STATUS_HTTP_400_BAD_REQUEST:
throw new InvalidRequestException("Server responded with: " + IOUtils.toString(theResponseReader));
case Constants.STATUS_HTTP_404_NOT_FOUND:
throw new ResourceNotFoundException("Server responded with: " + IOUtils.toString(theResponseReader));
case Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED:
throw new MethodNotAllowedException("Server responded with: " + IOUtils.toString(theResponseReader));
case Constants.STATUS_HTTP_409_CONFLICT:
throw new ResourceVersionConflictException("Server responded with: " + IOUtils.toString(theResponseReader));
case Constants.STATUS_HTTP_412_PRECONDITION_FAILED:
throw new ResourceVersionNotSpecifiedException("Server responded with: " + IOUtils.toString(theResponseReader));
case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY:
IParser parser = createAppropriateParser(theResponseMimeType, theResponseReader, theResponseStatusCode);
OperationOutcome operationOutcome = parser.parseResource(OperationOutcome.class, theResponseReader);
throw new UnprocessableEntityException(operationOutcome);
default: default:
throw new UnclassifiedServerFailureException(theResponseStatusCode, IOUtils.toString(theResponseReader)); throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseReader);
} }
} }
@Override @Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
EncodingEnum encoding = BaseMethodBinding.determineResponseEncoding(theRequest.getServletRequest(), theRequest.getParameters()); EncodingEnum encoding = determineResponseEncoding(theRequest);
IParser parser = encoding.newParser(getContext()); IParser parser = encoding.newParser(getContext());
IResource resource; IResource resource;
if (requestContainsResource()) { if (requestContainsResource()) {
resource = parser.parseResource(theRequest.getInputReader()); resource = parser.parseResource(theRequest.getInputReader());
TagList tagList = new TagList();
for (Enumeration<String> enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) {
String nextTagComplete = enumeration.nextElement();
StringBuilder next = new StringBuilder(nextTagComplete);
parseTagValue(tagList, nextTagComplete, next);
}
if (tagList.isEmpty() == false) {
resource.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
}
} else { } else {
resource = null; resource = null;
} }
Object[] params = new Object[getParameters().size()]; Object[] params = createParametersForServerRequest(theRequest, resource);
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
if (param == null) {
continue;
}
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, resource);
}
addParametersForServerRequest(theRequest, params); addParametersForServerRequest(theRequest, params);
MethodOutcome response; MethodOutcome response;
try { try {
response = (MethodOutcome) invokeServerMethod(getProvider(), params); response = (MethodOutcome) invokeServerMethod(params);
} catch (InternalErrorException e) { } catch (InternalErrorException e) {
ourLog.error("Internal error during method invocation", e); ourLog.error("Internal error during method invocation", e);
streamOperationOutcome(e, theServer, encoding, theResponse, theRequest); streamOperationOutcome(e, theServer, encoding, theResponse, theRequest);
@ -183,10 +189,6 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
// getMethod().in // getMethod().in
} }
public boolean isReturnVoid() {
return myReturnVoid;
}
/* /*
* @Override public void invokeServer(RestfulServer theServer, Request * @Override public void invokeServer(RestfulServer theServer, Request
* theRequest, HttpServletResponse theResponse) throws * theRequest, HttpServletResponse theResponse) throws
@ -232,23 +234,8 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
* writer.close(); } // getMethod().in } * writer.close(); } // getMethod().in }
*/ */
@Override public boolean isReturnVoid() {
public boolean matches(Request theRequest) { return myReturnVoid;
Set<RequestType> allowableRequestTypes = provideAllowableRequestTypes();
RequestType requestType = theRequest.getRequestType();
if (!allowableRequestTypes.contains(requestType)) {
return false;
}
if (!getResourceName().equals(theRequest.getResourceName())) {
return false;
}
if (getMatchingOperation() == null && StringUtils.isNotBlank(theRequest.getOperation())) {
return false;
}
if (getMatchingOperation() != null && !getMatchingOperation().equals(theRequest.getOperation())) {
return false;
}
return true;
} }
private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response) { private void addLocationHeader(Request theRequest, HttpServletResponse theResponse, MethodOutcome response) {
@ -265,6 +252,78 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
theResponse.addHeader("Location", b.toString()); theResponse.addHeader("Location", b.toString());
} }
private void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) {
int firstSemicolon = theBuffer.indexOf(";");
int deleteTo;
if (firstSemicolon == -1) {
firstSemicolon = theBuffer.indexOf(",");
if (firstSemicolon == -1) {
firstSemicolon = theBuffer.length();
deleteTo = theBuffer.length();
} else {
deleteTo = firstSemicolon;
}
} else {
deleteTo = firstSemicolon + 1;
}
String term = theBuffer.substring(0, firstSemicolon);
String scheme = null;
String label = null;
if (isBlank(term)) {
return;
}
theBuffer.delete(0, deleteTo);
while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') {
theBuffer.deleteCharAt(0);
}
while (theBuffer.length() > 0) {
boolean foundSomething = false;
if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) {
int closeIdx = theBuffer.indexOf("\"", SCHEME.length());
scheme = theBuffer.substring(SCHEME.length(), closeIdx);
theBuffer.delete(0, closeIdx + 1);
foundSomething = true;
}
if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) {
int closeIdx = theBuffer.indexOf("\"", LABEL.length());
label = theBuffer.substring(LABEL.length(), closeIdx);
theBuffer.delete(0, closeIdx + 1);
foundSomething = true;
}
// TODO: support enc2231-string as described in
// http://tools.ietf.org/html/draft-johnston-http-category-header-02
// TODO: support multiple tags in one header as described in
// http://hl7.org/implement/standards/fhir/http.html#tags
while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) {
theBuffer.deleteCharAt(0);
}
if (!foundSomething) {
break;
}
}
if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') {
theBuffer.deleteCharAt(0);
while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') {
theBuffer.deleteCharAt(0);
}
theTagList.add(new Tag(term, label, scheme));
parseTagValue(theTagList, theCompleteHeaderValue, theBuffer);
} else {
theTagList.add(new Tag(term, label, scheme));
}
if (theBuffer.length() > 0) {
ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue);
}
}
protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams); protected abstract void addParametersForServerRequest(Request theRequest, Object[] theParams);
/** /**

View File

@ -22,13 +22,19 @@ package ca.uhn.fhir.rest.method;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.client.BaseClientInvocation; import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ResourceParameter; import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -88,7 +94,18 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
throw new NullPointerException("Resource can not be null"); throw new NullPointerException("Resource can not be null");
} }
return createClientInvocation(theArgs, resource); BaseClientInvocation retVal = createClientInvocation(theArgs, resource);
TagList list = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
if (list != null) {
for (Tag tag : list) {
if (StringUtils.isNotBlank(tag.getTerm())) {
retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue());
}
}
}
return retVal;
} }
} }

View File

@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.method;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -56,7 +56,6 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
@ -92,8 +91,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} else if (Bundle.class.isAssignableFrom(methodReturnType)) { } else if (Bundle.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.BUNDLE; myMethodReturnType = MethodReturnTypeEnum.BUNDLE;
} else { } else {
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
+ theMethod.getDeclaringClass().getCanonicalName());
} }
myResourceType = theReturnResourceType; myResourceType = theReturnResourceType;
@ -119,14 +117,14 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
@Override @Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException { public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException {
IParser parser = createAppropriateParser(theResponseMimeType, theResponseReader, theResponseStatusCode); IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode);
switch (getReturnType()) { switch (getReturnType()) {
case BUNDLE: { case BUNDLE: {
Bundle bundle; Bundle bundle;
if (myResourceType != null) { if (myResourceType != null) {
bundle = parser.parseBundle(myResourceType, theResponseReader); bundle = parser.parseBundle(myResourceType, theResponseReader);
}else { } else {
bundle = parser.parseBundle(theResponseReader); bundle = parser.parseBundle(theResponseReader);
} }
switch (getMethodReturnType()) { switch (getMethodReturnType()) {
@ -160,7 +158,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
InstantDt lmValue = new InstantDt(lmHeaders.get(0)); InstantDt lmValue = new InstantDt(lmHeaders.get(0));
resource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); resource.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue);
} catch (Exception e) { } catch (Exception e) {
// TODO: This shouldn't be thrown - Time format is not in InstandDt format for this header, find examples online // TODO: This shouldn't be thrown - Time format is not in
// InstandDt format for this header, find examples online
} }
} }
@ -179,7 +178,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
throw new IllegalStateException("Should not get here!"); throw new IllegalStateException("Should not get here!");
} }
public abstract List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException; public abstract List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException;
@Override @Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
@ -199,7 +198,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} }
// Determine response encoding // Determine response encoding
EncodingEnum responseEncoding = determineResponseEncoding(theRequest.getServletRequest(), requestParams); EncodingEnum responseEncoding = determineResponseEncoding(theRequest);
// Is this request coming from a browser // Is this request coming from a browser
String uaHeader = theRequest.getServletRequest().getHeader("user-agent"); String uaHeader = theRequest.getServletRequest().getHeader("user-agent");
@ -217,7 +216,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} }
} }
List<IResource> result = invokeServer(getProvider(), theRequest, params); List<IResource> result = invokeServer(theRequest, params);
switch (getReturnType()) { switch (getReturnType()) {
case BUNDLE: case BUNDLE:
streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode); streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode);
@ -233,7 +232,6 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} }
} }
protected static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { protected static IdDt getIdFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
Object retValObj = theResourceMetadata.get(theKey); Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) { if (retValObj == null) {
@ -251,8 +249,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return (IdDt) retValObj; return (IdDt) retValObj;
} }
} }
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName());
+ IdDt.class.getCanonicalName());
} }
private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) { private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
@ -268,8 +265,21 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return (InstantDt) retValObj; return (InstantDt) retValObj;
} }
} }
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName());
+ InstantDt.class.getCanonicalName()); }
private TagList getTagListFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
Object retValObj = theResourceMetadata.get(theKey);
if (retValObj == null) {
return null;
} else if (retValObj instanceof TagList) {
if (((TagList) retValObj).isEmpty()) {
return null;
} else {
return (TagList) retValObj;
}
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + TagList.class.getCanonicalName());
} }
private IParser getNewParser(EncodingEnum theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) { private IParser getNewParser(EncodingEnum theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
@ -286,8 +296,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS); return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
} }
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingEnum theResponseEncoding, String theServerBase, private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException { NarrativeModeEnum theNarrativeMode) throws IOException {
assert !theServerBase.endsWith("/"); assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200); theHttpResponse.setStatus(200);
@ -300,7 +310,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
theHttpResponse.setContentType(theResponseEncoding.getBundleContentType()); theHttpResponse.setContentType(theResponseEncoding.getBundleContentType());
} }
theHttpResponse.setCharacterEncoding("UTF-8"); theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theHttpResponse); theServer.addHeadersToResponse(theHttpResponse);
@ -325,7 +335,6 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} }
} }
RuntimeResourceDefinition def = getContext().getResourceDefinition(next); RuntimeResourceDefinition def = getContext().getResourceDefinition(next);
if (next.getId() != null && StringUtils.isNotBlank(next.getId().getValue())) { if (next.getId() != null && StringUtils.isNotBlank(next.getId().getValue())) {
@ -341,10 +350,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
b.append(resId); b.append(resId);
/* /*
* If this is a history operation, we add the version of the resource to the self link to indicate the version * If this is a history operation, we add the version of the
* resource to the self link to indicate the version
*/ */
if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE || getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
|| getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID); IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID);
if (versionId != null) { if (versionId != null) {
b.append('/'); b.append('/');
@ -368,6 +377,13 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
entry.setUpdated(updated); entry.setUpdated(updated);
} }
TagList tagList = getTagListFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.TAG_LIST);
if (tagList!=null) {
for (Tag nextTag : tagList) {
entry.addCategory(nextTag);
}
}
boolean haveQ = false; boolean haveQ = false;
if (thePrettyPrint) { if (thePrettyPrint) {
b.append('?').append(Constants.PARAM_PRETTY).append("=true"); b.append('?').append(Constants.PARAM_PRETTY).append("=true");
@ -406,8 +422,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} }
} }
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
theHttpResponse.setStatus(200); theHttpResponse.setStatus(200);
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) { if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
@ -417,7 +432,9 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} else { } else {
theHttpResponse.setContentType(theResponseEncoding.getResourceContentType()); theHttpResponse.setContentType(theResponseEncoding.getResourceContentType());
} }
theHttpResponse.setCharacterEncoding("UTF-8"); theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theHttpResponse);
InstantDt lastUpdated = getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ResourceMetadataKeyEnum.UPDATED); InstantDt lastUpdated = getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), ResourceMetadataKeyEnum.UPDATED);
if (lastUpdated != null) { if (lastUpdated != null) {

View File

@ -33,8 +33,8 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.rest.client.GetClientInvocation; import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding { public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding {
@ -71,19 +71,13 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
} }
@Override @Override
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { public List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws BaseServerResponseException {
IResource conf; IResource conf = (IResource) invokeServerMethod(theMethodParams);
try {
conf = (Conformance) invokeServerMethod(theResourceProvider, theMethodParams);
} catch (Exception e) {
throw new InternalErrorException("Failed to call access method", e);
}
return Collections.singletonList(conf); return Collections.singletonList(conf);
} }
@Override @Override
public boolean matches(Request theRequest) { public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (theRequest.getRequestType() == RequestType.OPTIONS) { if (theRequest.getRequestType() == RequestType.OPTIONS) {
return true; return true;
} }

View File

@ -39,9 +39,9 @@ import ca.uhn.fhir.rest.client.DeleteClientInvocation;
import ca.uhn.fhir.rest.client.PostClientInvocation; import ca.uhn.fhir.rest.client.PostClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
@ -67,12 +67,12 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
} }
} }
myIdParameterIndex = Util.findIdParameterIndex(theMethod); myIdParameterIndex = ParameterUtil.findIdParameterIndex(theMethod);
if (myIdParameterIndex == null) { if (myIdParameterIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation"); throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation");
} }
Integer versionIdParameterIndex = Util.findVersionIdParameterIndex(theMethod); Integer versionIdParameterIndex = ParameterUtil.findVersionIdParameterIndex(theMethod);
if (versionIdParameterIndex != null) { if (versionIdParameterIndex != null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has a parameter annotated with the @" + VersionIdParam.class.getSimpleName() throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has a parameter annotated with the @" + VersionIdParam.class.getSimpleName()
+ " annotation but delete methods may not have this annotation"); + " annotation but delete methods may not have this annotation");
@ -118,12 +118,6 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
return myResourceName; return myResourceName;
} }
// @Override
// public boolean matches(Request theRequest) {
// // TODO Auto-generated method stub
// return super.matches(theRequest);
// }
@Override @Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
IdDt idDt = (IdDt) theArgs[myIdParameterIndex]; IdDt idDt = (IdDt) theArgs[myIdParameterIndex];

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.rest.method;
import java.lang.reflect.Method;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.DeleteTags;
public class DeleteTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
public DeleteTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, DeleteTags theDeleteTags) {
super(theMethod, theConetxt, theProvider, theDeleteTags.type());
}
@Override
protected boolean isDelete() {
return true;
}
}

View File

@ -0,0 +1,182 @@
package ca.uhn.fhir.rest.method;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class GetTagsMethodBinding extends BaseMethodBinding {
private Class<? extends IResource> myType;
private Integer myIdParamIndex;
private Integer myVersionIdParamIndex;
private String myResourceName;
public GetTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, GetTags theAnnotation) {
super(theMethod, theConetxt, theProvider);
if (theProvider instanceof IResourceProvider) {
myType = ((IResourceProvider) theProvider).getResourceType();
} else {
myType = theAnnotation.type();
}
if (!IResource.class.equals(myType)) {
myResourceName = theConetxt.getResourceDefinition(myType).getName();
}
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod);
myVersionIdParamIndex = ParameterUtil.findVersionIdParameterIndex(theMethod);
if (myIdParamIndex != null && myType.equals(IResource.class)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' does not specify a resource type, but has an @" + IdParam.class.getSimpleName() + " parameter. Please specity a resource type in the @" + GetTags.class.getSimpleName() + " annotation");
}
}
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
if (theResponseStatusCode == Constants.STATUS_HTTP_200_OK) {
IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theResponseStatusCode);
TagList retVal = parser.parseTagList(theResponseReader);
return retVal;
} else {
throw processNon2xxResponseAndReturnExceptionToThrow(theResponseStatusCode, theResponseMimeType, theResponseReader);
}
}
@Override
public String getResourceName() {
return myResourceName;
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
return null;
}
@Override
public RestfulOperationSystemEnum getSystemOperationType() {
return null;
}
@Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
GetClientInvocation retVal;
IdDt id = null;
IdDt versionId = null;
if (myIdParamIndex != null) {
id = (IdDt) theArgs[myIdParamIndex];
if (myVersionIdParamIndex != null) {
versionId = (IdDt) theArgs[myVersionIdParamIndex];
}
}
if (myType != IResource.class) {
if (id != null) {
if (versionId != null) {
retVal = new GetClientInvocation(getResourceName(), id.getValue(), Constants.PARAM_HISTORY, versionId.getValue(), Constants.PARAM_TAGS);
} else {
retVal = new GetClientInvocation(getResourceName(), id.getValue(), Constants.PARAM_TAGS);
}
} else {
retVal = new GetClientInvocation(getResourceName(), Constants.PARAM_TAGS);
}
} else {
retVal = new GetClientInvocation(Constants.PARAM_TAGS);
}
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], null, retVal);
}
}
return retVal;
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
Object[] params = createParametersForServerRequest(theRequest, null);
if (myIdParamIndex != null) {
params[myIdParamIndex] = theRequest.getId();
}
if (myVersionIdParamIndex != null) {
params[myVersionIdParamIndex] = theRequest.getVersionId();
}
TagList resp = (TagList) invokeServerMethod(params);
EncodingEnum responseEncoding = determineResponseEncoding(theRequest);
theResponse.setContentType(responseEncoding.getResourceContentType());
theResponse.setStatus(Constants.STATUS_HTTP_200_OK);
theResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
theServer.addHeadersToResponse(theResponse);
IParser parser = responseEncoding.newParser(getContext());
parser.setPrettyPrint(prettyPrintResponse(theRequest));
PrintWriter writer = theResponse.getWriter();
try {
parser.encodeTagListToWriter(resp, writer);
} finally {
writer.close();
}
}
@Override
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (theRequest.getRequestType()!=RequestType.GET) {
return false;
}
if (!Constants.PARAM_TAGS.equals(theRequest.getOperation())) {
return false;
}
if (myResourceName == null) {
if (getResourceName() != null) {
return false;
}
} else if (!myResourceName.equals(theRequest.getResourceName())) {
return false;
}
if ((myIdParamIndex != null) != (theRequest.getId() != null)) {
return false;
}
if ((myVersionIdParamIndex != null) != (theRequest.getVersionId() != null)) {
return false;
}
return true;
}
}

View File

@ -37,6 +37,7 @@ import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.client.BaseClientInvocation; import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.GetClientInvocation; import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -52,7 +53,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider); super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider);
myIdParamIndex = Util.findIdParameterIndex(theMethod); myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod);
History historyAnnotation = theMethod.getAnnotation(History.class); History historyAnnotation = theMethod.getAnnotation(History.class);
Class<? extends IResource> type = historyAnnotation.type(); Class<? extends IResource> type = historyAnnotation.type();
@ -142,12 +143,12 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
} }
@Override @Override
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { public List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
if (myIdParamIndex != null) { if (myIdParamIndex != null) {
theMethodParams[myIdParamIndex] = theRequest.getId(); theMethodParams[myIdParamIndex] = theRequest.getId();
} }
Object response = invokeServerMethod(theResourceProvider, theMethodParams); Object response = invokeServerMethod(theMethodParams);
List<IResource> resources = toResourceList(response); List<IResource> resources = toResourceList(response);
int index=0; int index=0;
@ -168,7 +169,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
// ObjectUtils.equals is replaced by a JDK7 method.. // ObjectUtils.equals is replaced by a JDK7 method..
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public boolean matches(Request theRequest) { public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) { if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
return false; return false;
} }
@ -185,7 +186,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
return false; return false;
} }
if (theRequest.getVersion() != null && !theRequest.getVersion().isEmpty()) { if (theRequest.getVersionId() != null && !theRequest.getVersionId().isEmpty()) {
return false; return false;
} }

View File

@ -35,6 +35,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.GetClientInvocation; import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -50,8 +51,8 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
Validate.notNull(theMethod, "Method must not be null"); Validate.notNull(theMethod, "Method must not be null");
Integer idIndex = Util.findIdParameterIndex(theMethod); Integer idIndex = ParameterUtil.findIdParameterIndex(theMethod);
Integer versionIdIndex = Util.findVersionIdParameterIndex(theMethod); Integer versionIdIndex = ParameterUtil.findVersionIdParameterIndex(theMethod);
myIdIndex = idIndex; myIdIndex = idIndex;
myVersionIdIndex = versionIdIndex; myVersionIdIndex = versionIdIndex;
@ -71,7 +72,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
} }
@Override @Override
public boolean matches(Request theRequest) { public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (!theRequest.getResourceName().equals(getResourceName())) { if (!theRequest.getResourceName().equals(getResourceName())) {
return false; return false;
} }
@ -80,7 +81,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
return false; return false;
} }
} }
if ((theRequest.getVersion() == null) != (myVersionIdIndex == null)) { if ((theRequest.getVersionId() == null) != (myVersionIdIndex == null)) {
return false; return false;
} }
if (theRequest.getId() == null) { if (theRequest.getId() == null) {
@ -106,13 +107,13 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
} }
@Override @Override
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { public List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
theMethodParams[myIdIndex] = theRequest.getId(); theMethodParams[myIdIndex] = theRequest.getId();
if (myVersionIdIndex != null) { if (myVersionIdIndex != null) {
theMethodParams[myVersionIdIndex] = theRequest.getVersion(); theMethodParams[myVersionIdIndex] = theRequest.getVersionId();
} }
Object response = invokeServerMethod(theResourceProvider, theMethodParams); Object response = invokeServerMethod(theMethodParams);
return toResourceList(response); return toResourceList(response);
} }

View File

@ -41,6 +41,7 @@ public class Request {
private Map<String, String[]> myParameters; private Map<String, String[]> myParameters;
private RequestType myRequestType; private RequestType myRequestType;
private String myResourceName; private String myResourceName;
private String mySecondaryOperation;
private HttpServletRequest myServletRequest; private HttpServletRequest myServletRequest;
private HttpServletResponse myServletResponse; private HttpServletResponse myServletResponse;
private IdDt myVersion; private IdDt myVersion;
@ -77,6 +78,10 @@ public class Request {
return myResourceName; return myResourceName;
} }
public String getSecondaryOperation() {
return mySecondaryOperation;
}
public HttpServletRequest getServletRequest() { public HttpServletRequest getServletRequest() {
return myServletRequest; return myServletRequest;
} }
@ -85,7 +90,7 @@ public class Request {
return myServletResponse; return myServletResponse;
} }
public IdDt getVersion() { public IdDt getVersionId() {
return myVersion; return myVersion;
} }
@ -121,6 +126,10 @@ public class Request {
myResourceName = theResourceName; myResourceName = theResourceName;
} }
public void setSecondaryOperation(String theSecondaryOperation) {
mySecondaryOperation = theSecondaryOperation;
}
public void setServletRequest(HttpServletRequest theRequest) { public void setServletRequest(HttpServletRequest theRequest) {
myServletRequest = theRequest; myServletRequest = theRequest;
} }

View File

@ -103,24 +103,24 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
} }
@Override @Override
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { public List<IResource> invokeServer(Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
assert theRequest.getId() == null; assert theRequest.getId() == null;
assert theRequest.getVersion() == null; assert theRequest.getVersionId() == null;
Object response = invokeServerMethod(theResourceProvider, theMethodParams); Object response = invokeServerMethod(theMethodParams);
return toResourceList(response); return toResourceList(response);
} }
@Override @Override
public boolean matches(Request theRequest) { public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (!theRequest.getResourceName().equals(getResourceName())) { if (!theRequest.getResourceName().equals(getResourceName())) {
ourLog.trace("Method {} doesn't match because resource name {} != {}", getMethod().getName(), theRequest.getResourceName(), getResourceName()); ourLog.trace("Method {} doesn't match because resource name {} != {}", getMethod().getName(), theRequest.getResourceName(), getResourceName());
return false; return false;
} }
if (theRequest.getId() != null || theRequest.getVersion() != null) { if (theRequest.getId() != null || theRequest.getVersionId() != null) {
ourLog.trace("Method {} doesn't match because ID or Version are not null: {} - {}", theRequest.getId(), theRequest.getVersion()); ourLog.trace("Method {} doesn't match because ID or Version are not null: {} - {}", theRequest.getId(), theRequest.getVersionId());
return false; return false;
} }
if (theRequest.getRequestType() == RequestType.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) { if (theRequest.getRequestType() == RequestType.GET && theRequest.getOperation() != null && !Constants.PARAM_SEARCH.equals(theRequest.getOperation())) {

View File

@ -41,6 +41,7 @@ import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.PutClientInvocation; import ca.uhn.fhir.rest.client.PutClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -53,11 +54,11 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
super(theMethod, theContext, Update.class, theProvider); super(theMethod, theContext, Update.class, theProvider);
myIdParameterIndex = Util.findIdParameterIndex(theMethod); myIdParameterIndex = ParameterUtil.findIdParameterIndex(theMethod);
if (myIdParameterIndex == null) { if (myIdParameterIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation"); throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation");
} }
myVersionIdParameterIndex = Util.findVersionIdParameterIndex(theMethod); myVersionIdParameterIndex = ParameterUtil.findVersionIdParameterIndex(theMethod);
} }
@Override @Override
@ -89,13 +90,9 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
} }
} }
// TODO: we should send an HTTP 412 automatically if the server
// only has a method which requires a version ID, and no
// Content-Location header is present
theParams[myIdParameterIndex] = theRequest.getId(); theParams[myIdParameterIndex] = theRequest.getId();
if (myVersionIdParameterIndex != null) { if (myVersionIdParameterIndex != null) {
theParams[myVersionIdParameterIndex] = theRequest.getVersion(); theParams[myVersionIdParameterIndex] = theRequest.getVersionId();
} }
} }
@ -146,10 +143,10 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
} }
@Override @Override
public boolean matches(Request theRequest) { public boolean incomingServerRequestMatchesMethod(Request theRequest) {
if (super.matches(theRequest)) { if (super.incomingServerRequestMatchesMethod(theRequest)) {
if (myVersionIdParameterIndex != null) { if (myVersionIdParameterIndex != null) {
if (theRequest.getVersion() == null) { if (theRequest.getVersionId() == null) {
return false; return false;
} }
} }

View File

@ -21,15 +21,10 @@ package ca.uhn.fhir.rest.method;
*/ */
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
@ -38,32 +33,6 @@ class Util {
// return findParamIndex(theMethod, Count.class); // return findParamIndex(theMethod, Count.class);
// } // }
public static Integer findIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, IdParam.class);
}
private static Integer findParamIndex(Method theMethod, Class<?> toFind) {
int paramIndex = 0;
for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
Annotation nextAnnotation = annotations[annotationIndex];
Class<? extends Annotation> class1 = nextAnnotation.getClass();
if (toFind.isAssignableFrom(class1)) {
return paramIndex;
}
}
paramIndex++;
}
return null;
}
// public static Integer findSinceParameterIndex(Method theMethod) {
// return findParamIndex(theMethod, Since.class);
// }
public static Integer findVersionIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, VersionIdParam.class);
}
public static Map<String, String> getQueryParams(String query) { public static Map<String, String> getQueryParams(String query) {
try { try {

View File

@ -34,6 +34,7 @@ import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.PostClientInvocation; import ca.uhn.fhir.rest.client.PostClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter; import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
@ -43,7 +44,7 @@ public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWith
public ValidateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { public ValidateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
super(theMethod, theContext, Validate.class, theProvider); super(theMethod, theContext, Validate.class, theProvider);
myIdParameterIndex = Util.findIdParameterIndex(theMethod); myIdParameterIndex = ParameterUtil.findIdParameterIndex(theMethod);
} }
@Override @Override

View File

@ -27,7 +27,6 @@ import java.util.Map;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.client.BaseClientInvocation; import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.PutClientInvocation;
import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;

View File

@ -27,6 +27,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -44,6 +45,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification; import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.TagListParam;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.Count; import ca.uhn.fhir.rest.annotation.Count;
@ -59,6 +61,84 @@ import ca.uhn.fhir.util.ReflectionUtil;
public class ParameterUtil { public class ParameterUtil {
private static final Set<Class<?>> BINDABLE_INTEGER_TYPES;
private static final Set<Class<?>> BINDABLE_TIME_TYPES;
static {
HashSet<Class<?>> intTypes = new HashSet<Class<?>>();
intTypes.add(IntegerDt.class);
intTypes.add(Integer.class);
BINDABLE_INTEGER_TYPES = Collections.unmodifiableSet(intTypes);
HashSet<Class<?>> timeTypes = new HashSet<Class<?>>();
timeTypes.add(InstantDt.class);
timeTypes.add(Date.class);
timeTypes.add(Calendar.class);
BINDABLE_TIME_TYPES = Collections.unmodifiableSet(timeTypes);
}
public static Integer findIdParameterIndex(Method theMethod) {
return findParamAnnotationIndex(theMethod, IdParam.class);
}
// public static Integer findSinceParameterIndex(Method theMethod) {
// return findParamIndex(theMethod, Since.class);
// }
public static Integer findTagListParameterIndex(Method theMethod) {
return findParamAnnotationIndex(theMethod, TagListParam.class);
}
public static Integer findVersionIdParameterIndex(Method theMethod) {
return findParamAnnotationIndex(theMethod, VersionIdParam.class);
}
public static Object fromInstant(Class<?> theType, InstantDt theArgument) {
if (theType.equals(InstantDt.class)) {
if (theArgument == null) {
return new InstantDt();
}
return theArgument;
}
if (theType.equals(Date.class)) {
if (theArgument == null) {
return null;
}
return theArgument.getValue();
}
if (theType.equals(Calendar.class)) {
if (theArgument == null) {
return null;
}
return DateUtils.toCalendar(theArgument.getValue());
}
throw new IllegalArgumentException("Invalid instant type:" + theType);
}
public static Object fromInteger(Class<?> theType, IntegerDt theArgument) {
if (theType.equals(IntegerDt.class)) {
if (theArgument == null) {
return new IntegerDt();
}
return theArgument;
}
if (theType.equals(Integer.class)) {
if (theArgument == null) {
return null;
}
return theArgument.getValue();
}
throw new IllegalArgumentException("Invalid Integer type:" + theType);
}
public static Set<Class<?>> getBindableInstantTypes() {
return BINDABLE_TIME_TYPES;
}
public static Set<Class<?>> getBindableIntegerTypes() {
return BINDABLE_INTEGER_TYPES;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static List<IParameter> getResourceParameters(Method theMethod) { public static List<IParameter> getResourceParameters(Method theMethod) {
List<IParameter> parameters = new ArrayList<IParameter>(); List<IParameter> parameters = new ArrayList<IParameter>();
@ -72,7 +152,8 @@ public class ParameterUtil {
Class<? extends java.util.Collection<?>> outerCollectionType = null; Class<? extends java.util.Collection<?>> outerCollectionType = null;
Class<? extends java.util.Collection<?>> innerCollectionType = null; Class<? extends java.util.Collection<?>> innerCollectionType = null;
if (TagList.class.isAssignableFrom(parameterType)) { if (TagList.class.isAssignableFrom(parameterType)) {
param = new TagListParameter(); // TagList is handled directly within the method bindings
param=new NullParameter();
} else { } else {
if (Collection.class.isAssignableFrom(parameterType)) { if (Collection.class.isAssignableFrom(parameterType)) {
innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType; innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
@ -158,19 +239,6 @@ public class ParameterUtil {
return parameters; return parameters;
} }
private static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) {
for (Annotation annotation : theAnnotations) {
if (annotation instanceof Description) {
Description desc = (Description) annotation;
if (isNotBlank(desc.formalDefinition())) {
theParameter.setDescription(desc.formalDefinition());
} else {
theParameter.setDescription(desc.shortDefinition());
}
}
}
}
public static InstantDt toInstant(Object theArgument) { public static InstantDt toInstant(Object theArgument) {
if (theArgument instanceof InstantDt) { if (theArgument instanceof InstantDt) {
return (InstantDt) theArgument; return (InstantDt) theArgument;
@ -184,37 +252,6 @@ public class ParameterUtil {
return null; return null;
} }
public static Set<Class<?>> getBindableInstantTypes() {
// TODO: make this constant
HashSet<Class<?>> retVal = new HashSet<Class<?>>();
retVal.add(InstantDt.class);
retVal.add(Date.class);
retVal.add(Calendar.class);
return retVal;
}
public static Object fromInstant(Class<?> theType, InstantDt theArgument) {
if (theType.equals(InstantDt.class)) {
if (theArgument == null) {
return new InstantDt();
}
return theArgument;
}
if (theType.equals(Date.class)) {
if (theArgument == null) {
return null;
}
return theArgument.getValue();
}
if (theType.equals(Calendar.class)) {
if (theArgument == null) {
return null;
}
return DateUtils.toCalendar(theArgument.getValue());
}
throw new IllegalArgumentException("Invalid instant type:" + theType);
}
public static IntegerDt toInteger(Object theArgument) { public static IntegerDt toInteger(Object theArgument) {
if (theArgument instanceof IntegerDt) { if (theArgument instanceof IntegerDt) {
return (IntegerDt) theArgument; return (IntegerDt) theArgument;
@ -225,28 +262,32 @@ public class ParameterUtil {
return null; return null;
} }
public static Set<Class<?>> getBindableIntegerTypes() { private static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) {
// TODO: make this constant for (Annotation annotation : theAnnotations) {
HashSet<Class<?>> retVal = new HashSet<Class<?>>(); if (annotation instanceof Description) {
retVal.add(IntegerDt.class); Description desc = (Description) annotation;
retVal.add(Integer.class); if (isNotBlank(desc.formalDefinition())) {
return retVal; theParameter.setDescription(desc.formalDefinition());
} else {
theParameter.setDescription(desc.shortDefinition());
}
}
}
} }
public static Object fromInteger(Class<?> theType, IntegerDt theArgument) { private static Integer findParamAnnotationIndex(Method theMethod, Class<?> toFind) {
if (theType.equals(IntegerDt.class)) { int paramIndex = 0;
if (theArgument == null) { for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
return new IntegerDt(); for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
Annotation nextAnnotation = annotations[annotationIndex];
Class<? extends Annotation> class1 = nextAnnotation.getClass();
if (toFind.isAssignableFrom(class1)) {
return paramIndex;
} }
return theArgument;
} }
if (theType.equals(Integer.class)) { paramIndex++;
if (theArgument == null) { }
return null; return null;
} }
return theArgument.getValue();
}
throw new IllegalArgumentException("Invalid Integer type:" + theType);
}
} }

View File

@ -1,150 +0,0 @@
package ca.uhn.fhir.rest.param;
/*
* #%L
* HAPI FHIR Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* 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%
*/
import static org.apache.commons.lang3.StringUtils.*;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class TagListParameter implements IParameter {
private static final String SCHEME = "scheme=\"";
private static final String LABEL = "label=\"";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TagListParameter.class);
@Override
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, BaseClientInvocation theClientInvocation) throws InternalErrorException {
TagList list = (TagList) theSourceClientArgument;
if (list != null) {
for (Tag tag : list) {
if (StringUtils.isNotBlank(tag.getTerm())) {
theClientInvocation.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue());
}
}
}
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
TagList retVal = new TagList();
for (Enumeration<String> enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) {
String nextTagComplete = enumeration.nextElement();
StringBuilder next = new StringBuilder(nextTagComplete);
parseTagValue(retVal, nextTagComplete, next);
}
return retVal;
}
private void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) {
int firstSemicolon = theBuffer.indexOf(";");
int deleteTo;
if (firstSemicolon == -1) {
firstSemicolon = theBuffer.indexOf(",");
if (firstSemicolon == -1) {
firstSemicolon = theBuffer.length();
deleteTo = theBuffer.length();
} else {
deleteTo = firstSemicolon;
}
} else {
deleteTo = firstSemicolon + 1;
}
String term = theBuffer.substring(0, firstSemicolon);
String scheme = null;
String label = null;
if (isBlank(term)) {
return;
}
theBuffer.delete(0, deleteTo);
while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') {
theBuffer.deleteCharAt(0);
}
while (theBuffer.length() > 0) {
boolean foundSomething = false;
if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) {
int closeIdx = theBuffer.indexOf("\"", SCHEME.length());
scheme = theBuffer.substring(SCHEME.length(), closeIdx);
theBuffer.delete(0, closeIdx + 1);
foundSomething = true;
}
if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) {
int closeIdx = theBuffer.indexOf("\"", LABEL.length());
label = theBuffer.substring(LABEL.length(), closeIdx);
theBuffer.delete(0, closeIdx + 1);
foundSomething = true;
}
// TODO: support enc2231-string as described in
// http://tools.ietf.org/html/draft-johnston-http-category-header-02
// TODO: support multiple tags in one header as described in
// http://hl7.org/implement/standards/fhir/http.html#tags
while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) {
theBuffer.deleteCharAt(0);
}
if (!foundSomething) {
break;
}
}
if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') {
theBuffer.deleteCharAt(0);
while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') {
theBuffer.deleteCharAt(0);
}
theTagList.add(new Tag(term, label, scheme));
parseTagValue(theTagList, theCompleteHeaderValue, theBuffer);
} else {
theTagList.add(new Tag(term, label, scheme));
}
if (theBuffer.length() > 0) {
ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue);
}
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// TODO Auto-generated method stub
}
}

View File

@ -68,6 +68,9 @@ public class Constants {
public static final String PARAM_SORT_DESC = "_sort:desc"; public static final String PARAM_SORT_DESC = "_sort:desc";
public static final String HEADER_CATEGORY = "Category"; public static final String HEADER_CATEGORY = "Category";
public static final String OPENSEARCH_NS_OLDER = "http://purl.org/atompub/tombstones/1.0"; public static final String OPENSEARCH_NS_OLDER = "http://purl.org/atompub/tombstones/1.0";
public static final String PARAM_TAGS = "_tags";
public static final String CHARSET_UTF_8 = "UTF-8";
public static final String PARAM_DELETE = "_delete";
static { static {
Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>(); Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();

View File

@ -52,6 +52,16 @@ public enum EncodingEnum {
ourContentTypeToEncoding.put(next.getResourceContentType(), next); ourContentTypeToEncoding.put(next.getResourceContentType(), next);
ourContentTypeToEncoding.put(next.getBrowserFriendlyBundleContentType(), next); ourContentTypeToEncoding.put(next.getBrowserFriendlyBundleContentType(), next);
} }
/*
* These are wrong, but we add them just to be tolerant
* of other people's mistakes
*/
ourContentTypeToEncoding.put("application/json", JSON);
ourContentTypeToEncoding.put("application/xml", XML);
ourContentTypeToEncoding.put("text/json", JSON);
ourContentTypeToEncoding.put("text/xml", XML);
} }
private String myResourceContentType; private String myResourceContentType;

View File

@ -52,7 +52,7 @@ public class ResourceBinding {
ourLog.debug("Looking for a handler for {}", theRequest); ourLog.debug("Looking for a handler for {}", theRequest);
for (BaseMethodBinding rm : methods) { for (BaseMethodBinding rm : methods) {
if (rm.matches(theRequest)) { if (rm.incomingServerRequestMatchesMethod(theRequest)) {
ourLog.debug("Handler {} matches", rm); ourLog.debug("Handler {} matches", rm);
return rm; return rm;
} else { } else {

View File

@ -63,6 +63,7 @@ public class RestfulServer extends HttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private FhirContext myFhirContext; private FhirContext myFhirContext;
private ResourceBinding myNullResourceBinding = new ResourceBinding();
private Collection<Object> myPlainProviders; private Collection<Object> myPlainProviders;
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>(); private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
private Collection<IResourceProvider> myResourceProviders; private Collection<IResourceProvider> myResourceProviders;
@ -70,12 +71,10 @@ public class RestfulServer extends HttpServlet {
private BaseMethodBinding myServerConformanceMethod; private BaseMethodBinding myServerConformanceMethod;
private Object myServerConformanceProvider; private Object myServerConformanceProvider;
private String myServerName = "HAPI FHIR Server"; private String myServerName = "HAPI FHIR Server";
private String myServerVersion = VersionUtil.getVersion(); // defaults to /** This is configurable but by default we jsut use HAPI version */
// HAPI version private String myServerVersion = VersionUtil.getVersion();
private boolean myUseBrowserFriendlyContentTypes;
private ResourceBinding myNullResourceBinding = new ResourceBinding();
private boolean myStarted; private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
/** /**
* Constructor * Constructor
@ -90,12 +89,12 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* This method is called prior to sending a response to incoming requests. It is * This method is called prior to sending a response to incoming requests.
* used to add custom headers. * It is used to add custom headers.
* <p> * <p>
* Use caution if overriding this method: it is recommended to call * Use caution if overriding this method: it is recommended to call
* <code>super.addHeadersToResponse</code> to avoid inadvertantly * <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling
* disabling functionality. * functionality.
* </p> * </p>
*/ */
public void addHeadersToResponse(HttpServletResponse theHttpResponse) { public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -103,9 +102,9 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Gets the {@link FhirContext} associated with this server. For efficient processing, * Gets the {@link FhirContext} associated with this server. For efficient
* resource providers and plain providers should generally use this context * processing, resource providers and plain providers should generally use
* if one is needed, as opposed to creating their own. * this context if one is needed, as opposed to creating their own.
*/ */
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return myFhirContext; return myFhirContext;
@ -177,10 +176,10 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Initializes the server. Note that this method is final to avoid accidentally * Initializes the server. Note that this method is final to avoid
* introducing bugs in implementations, but subclasses may put initialization code in * accidentally introducing bugs in implementations, but subclasses may put
* {@link #initialize()}, which is called immediately before beginning initialization of * initialization code in {@link #initialize()}, which is called immediately
* the restful server's internal init. * before beginning initialization of the restful server's internal init.
*/ */
@Override @Override
public final void init() throws ServletException { public final void init() throws ServletException {
@ -190,7 +189,7 @@ public class RestfulServer extends HttpServlet {
mySecurityManager = getSecurityManager(); mySecurityManager = getSecurityManager();
if (null == mySecurityManager) { if (null == mySecurityManager) {
ourLog.warn("No security manager has been provided, requests will not be authenticated!"); ourLog.trace("No security manager has been provided");
} }
Collection<IResourceProvider> resourceProvider = getResourceProviders(); Collection<IResourceProvider> resourceProvider = getResourceProviders();
@ -233,12 +232,6 @@ public class RestfulServer extends HttpServlet {
ourLog.info("A FHIR has been lit on this server"); ourLog.info("A FHIR has been lit on this server");
} }
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Must be public");
}
}
public boolean isUseBrowserFriendlyContentTypes() { public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes; return myUseBrowserFriendlyContentTypes;
} }
@ -253,6 +246,16 @@ public class RestfulServer extends HttpServlet {
myPlainProviders = theProviders; myPlainProviders = theProviders;
} }
/**
* Sets the non-resource specific providers which implement method calls on
* this server.
*
* @see #setResourceProviders(Collection)
*/
public void setPlainProviders(Object... theProv) {
setPlainProviders(Arrays.asList(theProv));
}
/** /**
* Sets the non-resource specific providers which implement method calls on * Sets the non-resource specific providers which implement method calls on
* this server * this server
@ -336,6 +339,12 @@ public class RestfulServer extends HttpServlet {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes; myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
} }
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Must be public");
}
}
private void findResourceMethods(Object theProvider) throws Exception { private void findResourceMethods(Object theProvider) throws Exception {
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass()); ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
@ -373,7 +382,7 @@ public class RestfulServer extends HttpServlet {
} }
resourceBinding.addMethod(foundMethodBinding); resourceBinding.addMethod(foundMethodBinding);
ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
} else { } else {
ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName()); ourLog.debug(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName());
} }
@ -411,11 +420,6 @@ public class RestfulServer extends HttpServlet {
} }
} }
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
}
// /** // /**
// * Sets the {@link INarrativeGenerator Narrative Generator} to use when // * Sets the {@link INarrativeGenerator Narrative Generator} to use when
// serializing responses from this server, or <code>null</code> (which is // serializing responses from this server, or <code>null</code> (which is
@ -433,6 +437,11 @@ public class RestfulServer extends HttpServlet {
// myNarrativeGenerator = theNarrativeGenerator; // myNarrativeGenerator = theNarrativeGenerator;
// } // }
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
}
@Override @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.GET, request, response); handleRequest(SearchMethodBinding.RequestType.GET, request, response);
@ -473,10 +482,12 @@ public class RestfulServer extends HttpServlet {
servletContextPath = servletPath; servletContextPath = servletPath;
} }
ourLog.info("Request FullPath: {}", requestFullPath); if (ourLog.isTraceEnabled()) {
ourLog.info("Servlet Path: {}", servletPath); ourLog.trace("Request FullPath: {}", requestFullPath);
ourLog.info("Request Url: {}", requestUrl); ourLog.trace("Servlet Path: {}", servletPath);
ourLog.info("Context Path: {}", servletContextPath); ourLog.trace("Request Url: {}", requestUrl);
ourLog.trace("Context Path: {}", servletContextPath);
}
servletPath = servletContextPath; servletPath = servletContextPath;
@ -539,7 +550,12 @@ public class RestfulServer extends HttpServlet {
if (tok.hasMoreTokens()) { if (tok.hasMoreTokens()) {
String nextString = tok.nextToken(); String nextString = tok.nextToken();
if (nextString.startsWith("_")) { if (nextString.equals(Constants.PARAM_HISTORY)) {
if (tok.hasMoreTokens()) {
String versionString = tok.nextToken();
versionId = new IdDt(versionString);
}
} else if (nextString.startsWith("_")) {
if (operation != null) { if (operation != null) {
throw new InvalidRequestException("URL Path contains two operations (part beginning with _): " + requestPath); throw new InvalidRequestException("URL Path contains two operations (part beginning with _): " + requestPath);
} }
@ -547,14 +563,23 @@ public class RestfulServer extends HttpServlet {
} }
} }
if (tok.hasMoreTokens()) { // Secondary is for things like ..../_tags/_delete
String secondaryOperation=null;
while (tok.hasMoreTokens()) {
String nextString = tok.nextToken(); String nextString = tok.nextToken();
versionId = new IdDt(nextString); if (operation == null) {
operation = nextString;
}else if (secondaryOperation==null) {
secondaryOperation=nextString;
}else {
throw new InvalidRequestException("URL path has unexpected token '"+nextString + "' at the end: " + requestPath);
}
} }
if (theRequestType==RequestType.PUT && versionId==null) { if (theRequestType == RequestType.PUT && versionId == null) {
String contentLocation = theRequest.getHeader("Content-Location"); String contentLocation = theRequest.getHeader("Content-Location");
if (contentLocation!=null) { if (contentLocation != null) {
int idx = contentLocation.indexOf("/_history/"); int idx = contentLocation.indexOf("/_history/");
if (idx != -1) { if (idx != -1) {
String versionIdString = contentLocation.substring(idx + "/_history/".length()); String versionIdString = contentLocation.substring(idx + "/_history/".length());
@ -563,7 +588,6 @@ public class RestfulServer extends HttpServlet {
} }
} }
// TODO: look for more tokens for version, compartments, etc... // TODO: look for more tokens for version, compartments, etc...
Request r = new Request(); Request r = new Request();
@ -571,6 +595,7 @@ public class RestfulServer extends HttpServlet {
r.setId(id); r.setId(id);
r.setVersion(versionId); r.setVersion(versionId);
r.setOperation(operation); r.setOperation(operation);
r.setSecondaryOperation(secondaryOperation);
r.setParameters(params); r.setParameters(params);
r.setRequestType(theRequestType); r.setRequestType(theRequestType);
if ("application/x-www-form-urlencoded".equals(theRequest.getContentType())) { if ("application/x-www-form-urlencoded".equals(theRequest.getContentType())) {

View File

@ -26,6 +26,12 @@ import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
* #L% * #L%
*/ */
/**
* Base class for RESTful client and server exceptions. RESTful client methods
* will only throw exceptions which are subclasses of this exception type, and
* RESTful server methods should also only call subclasses of this exception
* type.
*/
public abstract class BaseServerResponseException extends RuntimeException { public abstract class BaseServerResponseException extends RuntimeException {
private static final Map<Integer, Class<? extends BaseServerResponseException>> ourStatusCodeToExceptionType = new HashMap<Integer, Class<? extends BaseServerResponseException>>(); private static final Map<Integer, Class<? extends BaseServerResponseException>> ourStatusCodeToExceptionType = new HashMap<Integer, Class<? extends BaseServerResponseException>>();
@ -44,6 +50,9 @@ public abstract class BaseServerResponseException extends RuntimeException {
private final OperationOutcome myOperationOutcome; private final OperationOutcome myOperationOutcome;
private String myResponseBody;
private String myResponseMimeType;
private int myStatusCode; private int myStatusCode;
/** /**
@ -105,12 +114,36 @@ public abstract class BaseServerResponseException extends RuntimeException {
} }
/** /**
* Returns the {@link OperationOutcome} resource if any which was supplied in the response, or <code>null</code> * Returns the {@link OperationOutcome} resource if any which was supplied
* in the response, or <code>null</code>
*/ */
public OperationOutcome getOperationOutcome() { public OperationOutcome getOperationOutcome() {
return myOperationOutcome; return myOperationOutcome;
} }
/**
* In a RESTful client, this method will be populated with the body of the
* HTTP respone if one was provided by the server, or <code>null</code>
* otherwise.
* <p>
* In a restful server, this method is currently ignored.
* </p>
*/
public String getResponseBody() {
return myResponseBody;
}
/**
* In a RESTful client, this method will be populated with the HTTP status
* code that was returned with the HTTP response.
* <p>
* In a restful server, this method is currently ignored.
* </p>
*/
public String getResponseMimeType() {
return myResponseMimeType;
}
/** /**
* Returns the HTTP status code corresponding to this problem * Returns the HTTP status code corresponding to this problem
*/ */
@ -118,6 +151,22 @@ public abstract class BaseServerResponseException extends RuntimeException {
return myStatusCode; return myStatusCode;
} }
/**
* This method is currently only called internally by HAPI, it should not be
* called by user code.
*/
public void setResponseBody(String theResponseBody) {
myResponseBody = theResponseBody;
}
/**
* This method is currently only called internally by HAPI, it should not be
* called by user code.
*/
public void setResponseMimeType(String theResponseMimeType) {
myResponseMimeType = theResponseMimeType;
}
public static BaseServerResponseException newInstance(int theStatusCode, String theMessage) { public static BaseServerResponseException newInstance(int theStatusCode, String theMessage) {
if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) { if (ourStatusCodeToExceptionType.containsKey(theStatusCode)) {
try { try {
@ -146,4 +195,5 @@ public abstract class BaseServerResponseException extends RuntimeException {
} }
ourStatusCodeToExceptionType.put(theStatusCode, theType); ourStatusCodeToExceptionType.put(theStatusCode, theType);
} }
} }

View File

@ -23,7 +23,20 @@ import ca.uhn.fhir.rest.server.Constants;
*/ */
/** /**
* TODO: javadoc this * Represents an <b>HTTP 500 Internal Error</b> response.
* This status indicates that the server failed to successfully process the
* request. This generally means that the server is misbehaving or is
* misconfigured in some way, although a misbehaving server might also
* send this status code in the case of a bad request message (although it
* should not do this; an HTTP 4xx response is more appropriate in that
* situation).
*
* <p>
* Note that a complete list of RESTful exceptions is available in the
* <a href="./package-summary.html">Package Summary</a>.
* </p>
*
* @see UnprocessableEntityException Which should be used for business level validation failures
*/ */
public class InternalErrorException extends BaseServerResponseException { public class InternalErrorException extends BaseServerResponseException {

View File

@ -23,11 +23,16 @@ import ca.uhn.fhir.rest.server.Constants;
*/ */
/** /**
* RESTful method exception which corresponds to the <b>HTTP 400 Bad Request</b> status. * Represents an <b>HTTP 400 Bad Request</b> response.
* This status indicates that the client's message was invalid (e.g. not a valid FHIR Resource * This status indicates that the client's message was invalid (e.g. not a valid FHIR Resource
* per the specifications), as opposed to the {@link InvalidRequestException} which indicates * per the specifications), as opposed to the {@link InvalidRequestException} which indicates
* that data does not pass business rule validation on the server. * that data does not pass business rule validation on the server.
* *
* <p>
* Note that a complete list of RESTful exceptions is available in the
* <a href="./package-summary.html">Package Summary</a>.
* </p>
*
* @see UnprocessableEntityException Which should be used for business level validation failures * @see UnprocessableEntityException Which should be used for business level validation failures
*/ */
public class InvalidRequestException extends BaseServerResponseException { public class InvalidRequestException extends BaseServerResponseException {

View File

@ -23,7 +23,14 @@ import ca.uhn.fhir.rest.server.Constants;
*/ */
/** /**
* TODO: javadoc this * Represents an <b>HTTP 405 Method Not Allowed</b> response.
*
* <p>
* Note that a complete list of RESTful exceptions is available in the
* <a href="./package-summary.html">Package Summary</a>.
* </p>
*
* @see UnprocessableEntityException Which should be used for business level validation failures
*/ */
public class MethodNotAllowedException extends BaseServerResponseException { public class MethodNotAllowedException extends BaseServerResponseException {
public static final int STATUS_CODE = Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED; public static final int STATUS_CODE = Constants.STATUS_HTTP_405_METHOD_NOT_ALLOWED;

View File

@ -46,7 +46,11 @@ public class ReflectionUtil {
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) { public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
Class<?> type; Class<?> type;
ParameterizedType collectionType = (ParameterizedType) theMethod.getGenericParameterTypes()[theParamIndex]; Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
if (Class.class.equals(genericParameterType)) {
return null;
}
ParameterizedType collectionType = (ParameterizedType) genericParameterType;
Type firstArg = collectionType.getActualTypeArguments()[0]; Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) { if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg); ParameterizedType pt = ((ParameterizedType) firstArg);
@ -60,7 +64,11 @@ public class ReflectionUtil {
@SuppressWarnings({ "unused", "rawtypes" }) @SuppressWarnings({ "unused", "rawtypes" })
public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) { public static Class<?> getGenericCollectionTypeOfMethodReturnType(Method theMethod) {
Class<?> type; Class<?> type;
ParameterizedType collectionType = (ParameterizedType) theMethod.getGenericReturnType(); Type genericReturnType = theMethod.getGenericReturnType();
if (!(genericReturnType instanceof ParameterizedType)) {
return null;
}
ParameterizedType collectionType = (ParameterizedType) genericReturnType;
Type firstArg = collectionType.getActualTypeArguments()[0]; Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) { if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg); ParameterizedType pt = ((ParameterizedType) firstArg);

View File

@ -0,0 +1,32 @@
dt.1=ca.uhn.fhir.model.dstu.composite.AddressDt
dt.2=ca.uhn.fhir.model.dstu.composite.AgeDt
dt.3=ca.uhn.fhir.model.dstu.composite.AttachmentDt
dt.4=ca.uhn.fhir.model.dstu.composite.CodeableConceptDt
dt.5=ca.uhn.fhir.model.dstu.composite.CodingDt
dt.6=ca.uhn.fhir.model.dstu.composite.ContactDt
dt.8=ca.uhn.fhir.model.dstu.composite.DurationDt
dt.9=ca.uhn.fhir.model.dstu.composite.HumanNameDt
dt.10=ca.uhn.fhir.model.dstu.composite.IdentifierDt
dt.11=ca.uhn.fhir.model.dstu.composite.NarrativeDt
dt.12=ca.uhn.fhir.model.dstu.composite.PeriodDt
dt.13=ca.uhn.fhir.model.dstu.composite.QuantityDt
dt.14=ca.uhn.fhir.model.dstu.composite.RangeDt
dt.15=ca.uhn.fhir.model.dstu.composite.RatioDt
dt.16=ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt
dt.17=ca.uhn.fhir.model.dstu.composite.SampledDataDt
dt.18=ca.uhn.fhir.model.dstu.composite.ScheduleDt
dt.31=ca.uhn.fhir.model.primitive.Base64BinaryDt
dt.33=ca.uhn.fhir.model.primitive.BooleanDt
dt.34=ca.uhn.fhir.model.primitive.BoundCodeableConceptDt
dt.35=ca.uhn.fhir.model.primitive.BoundCodeDt
dt.36=ca.uhn.fhir.model.primitive.CodeDt
dt.37=ca.uhn.fhir.model.primitive.DateDt
dt.38=ca.uhn.fhir.model.primitive.DateTimeDt
dt.39=ca.uhn.fhir.model.primitive.DecimalDt
dt.40=ca.uhn.fhir.model.primitive.IdDt
dt.42=ca.uhn.fhir.model.primitive.InstantDt
dt.43=ca.uhn.fhir.model.primitive.IntegerDt
dt.44=ca.uhn.fhir.model.primitive.OidDt
dt.45=ca.uhn.fhir.model.primitive.StringDt
dt.46=ca.uhn.fhir.model.primitive.UriDt

View File

@ -6,15 +6,17 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.HttpBasicAuthInterceptor; import ca.uhn.fhir.rest.client.HttpBasicAuthInterceptor;
import ca.uhn.fhir.rest.client.IRestfulClientFactory; import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class ClientExamples { public class ClientExamples {
public interface PatientClient extends IBasicClient { public interface IPatientClient extends IBasicClient {
// nothing yet // nothing yet
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
public void createSecurity() { public void createSecurity() {
{
//START SNIPPET: security //START SNIPPET: security
// Create a context and get the client factory so it can be configured // Create a context and get the client factory so it can be configured
FhirContext ctx = new FhirContext(); FhirContext ctx = new FhirContext();
@ -32,9 +34,25 @@ builder.addInterceptorFirst(new HttpBasicAuthInterceptor(username, password));
clientFactory.setHttpClient(builder.build()); clientFactory.setHttpClient(builder.build());
// Actually create a client instance // Actually create a client instance
PatientClient client = ctx.newRestfulClient(PatientClient.class, "http://localhost:9999/"); IPatientClient client = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/");
//END SNIPPET: security //END SNIPPET: security
}
/******************************/
{
//START SNIPPET: clientConfig
//Create a client
FhirContext ctx = new FhirContext();
IPatientClient client = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/");
// Request JSON encoding from the server (_format=json)
client.setEncoding(EncodingEnum.JSON);
// Request pretty printing from the server (_pretty=true)
client.setPrettyPrint(true);
//END SNIPPET: clientConfig
}
} }
} }

View File

@ -14,6 +14,7 @@ import ca.uhn.fhir.parser.DataFormatException;
public class Extensions { public class Extensions {
@SuppressWarnings("unused")
public static void main(String[] args) throws DataFormatException, IOException { public static void main(String[] args) throws DataFormatException, IOException {

View File

@ -14,7 +14,10 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification; import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.TagListParam;
import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Conformance;
@ -23,13 +26,17 @@ import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum; import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.Count; import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.History; import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.IncludeParam;
@ -508,8 +515,146 @@ System.out.println(ctx.newXmlParser().encodeResourceToString(metadata));
//END SNIPPET: metadataClientUsage //END SNIPPET: metadataClientUsage
} }
//START SNIPPET: readTags
@Read()
public Patient readPatient(@IdParam IdDt theId) {
Patient retVal = new Patient();
// ..populate demographics, contact, or anything else you usually would..
// Create a TagList and place a complete list of the patient's tags inside
TagList tags = new TagList();
tags.addTag("Dog", "Canine Patient", "http://animals"); // TODO: more realistic example
tags.addTag("Friendly", "Friendly", "http://personality"); // TODO: more realistic example
// The tags are then stored in the Patient resource instance
retVal.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tags);
return retVal;
}
//END SNIPPET: readTags
//START SNIPPET: clientReadInterface
private interface IPatientClient extends IBasicClient
{
/** Read a patient from a server by ID */
@Read
Patient readPatient(@IdParam IdDt theId);
// Only one method is shown here, but many methods may be
// added to the same client interface!
}
//START SNIPPET: clientReadInterface
public void clientRead() {
//START SNIPPET: clientReadTags
IPatientClient client = new FhirContext().newRestfulClient(IPatientClient.class, "http://foo/fhir");
Patient patient = client.readPatient(new IdDt("1234"));
// Access the tag list
TagList tagList = (TagList) patient.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
for (Tag next : tagList) {
// ..process the tags somehow..
}
//END SNIPPET: clientReadTags
//START SNIPPET: clientCreateTags
Patient newPatient = new Patient();
// Populate the resource object
newPatient.addIdentifier().setUse(IdentifierUseEnum.OFFICIAL).setValue("123");
newPatient.addName().addFamily("Jones").addGiven("Frank");
// Populate tags
TagList tags = new TagList();
tags.addTag("Dog", "Canine Patient", "http://animals"); // TODO: more realistic example
tags.addTag("Friendly", "Friendly", "http://personality"); // TODO: more realistic example
newPatient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tags);
// ...invoke the create method on the client...
//END SNIPPET: clientCreateTags
}
//START SNIPPET: createTags
@Create
public MethodOutcome createPatientResource(@ResourceParam Patient thePatient) {
// ..save the resouce..
IdDt id = new IdDt("123"); // the new databse primary key for this resource
// Get the tag list
TagList tags = (TagList) thePatient.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
for (Tag tag : tags) {
// process/save each tag somehow
}
return new MethodOutcome(id);
}
//END SNIPPET: createTags
//START SNIPPET: tagMethodProvider
public class TagMethodProvider
{
/** Return a list of all tags that exist on the server */
@GetTags
public TagList getAllTagsOnServer() {
return new TagList(); // populate this
}
/** Return a list of all tags that exist on at least one instance
* of the given resource type */
@GetTags(type=Patient.class)
public TagList getTagsForAllResourcesOfResourceType() {
return new TagList(); // populate this
}
/** Return a list of all tags that exist on a specific instance
* of the given resource type */
@GetTags(type=Patient.class)
public TagList getTagsForResources(@IdParam IdDt theId) {
return new TagList(); // populate this
}
/** Return a list of all tags that exist on a specific version
* of the given resource type */
@GetTags(type=Patient.class)
public TagList getTagsForResourceVersion(@IdParam IdDt theId,
@VersionIdParam IdDt theVersion) {
return new TagList(); // populate this
}
/** Add tags to a resource */
@AddTags(type=Patient.class)
public void getTagsForResourceVersion(@IdParam IdDt theId,
@TagListParam TagList theTagList) {
// add tags
}
/** Add tags to a resource version */
@AddTags(type=Patient.class)
public void addTagsToResourceVersion(@IdParam IdDt theId,
@VersionIdParam IdDt theVersion,
@TagListParam TagList theTagList) {
// add tags
}
/** Remove tags from a resource */
@DeleteTags(type=Patient.class)
public void deleteTagsFromResourceVersion(@IdParam IdDt theId,
@TagListParam TagList theTagList) {
// add tags
}
/** Remove tags from a resource version */
@DeleteTags(type=Patient.class)
public void deleteTagsFromResourceVersion(@IdParam IdDt theId,
@VersionIdParam IdDt theVersion,
@TagListParam TagList theTagList) {
// add tags
}
}
//END SNIPPET: tagMethodProvider
} }

View File

@ -122,7 +122,7 @@
</section> </section>
<section name="Configuring the HTTP Client"> <section name="Configuring the Client">
<p> <p>
The client uses <a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HTTP Client</a> The client uses <a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HTTP Client</a>
@ -155,6 +155,28 @@
</subsection> </subsection>
<subsection name="Configuring Encoding (JSON/XML)">
<p>
Restful client interfaces that you create will also extend
the interface
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>,
which comes with some helpful methods for configuring the way that
the client will interact with the server.
</p>
<p>
The following snippet shows how to configure the cliet to explicitly
request JSON or XML responses, and how to request "pretty printed" responses
on servers that support this (HAPI based servers currently).
</p>
<macro name="snippet">
<param name="id" value="clientConfig" />
<param name="file" value="src/site/example/java/example/ClientExamples.java" />
</macro>
</subsection>
</section> </section>
</body> </body>

View File

@ -14,15 +14,23 @@
Jump To... Jump To...
</p> </p>
<ul> <ul>
<li><a href="#operations">Operations</a></li> <li>
<li><a href="#exceptions">Exceptions</a></li> <a href="#operations">Operations</a>
</li>
<li>
<a href="#exceptions">Exceptions</a>
</li>
<li>
<a href="#tags">Tags</a>
</li>
</ul> </ul>
<p> <p>
RESTful Clients and Servers both share the same RESTful Clients and Servers both share the same
method pattern, with one key difference: A client method pattern, with one key difference: A client
is defined using annotated methods on an interface is defined using annotated methods on an interface
which are used to retrieve resources, which are used to retrieve
resources,
whereas a server requires concrete method whereas a server requires concrete method
implementations implementations
to actually provide those resources. to actually provide those resources.
@ -34,7 +42,7 @@
implementations, but client methods will follow the same patterns. implementations, but client methods will follow the same patterns.
</p> </p>
<a name="operations"/> <a name="operations" />
</section> </section>
<section name="Operations"> <section name="Operations">
@ -93,7 +101,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#type_search">Type - Create</a> <a href="#type_create">Type - Create</a>
</td> </td>
<td> <td>
Create a new resource with a server assigned id Create a new resource with a server assigned id
@ -101,7 +109,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<a href="#instance_update">Type - Search</a> <a href="#type_search">Type - Search</a>
<macro name="toc"> <macro name="toc">
<param name="section" value="8" /> <param name="section" value="8" />
<param name="fromDepth" value="2" /> <param name="fromDepth" value="2" />
@ -159,6 +167,14 @@
Search across all resource types based on some filter criteria Search across all resource types based on some filter criteria
</td> </td>
</tr> </tr>
<tr>
<td>
<a href="#tags">Tag Operations</a>
</td>
<td>
Search across all resource types based on some filter criteria
</td>
</tr>
</tbody> </tbody>
</table> </table>
@ -195,6 +211,16 @@
<code>http://fhir.example.com/Patient/111</code> <code>http://fhir.example.com/Patient/111</code>
</p> </p>
<p>
The following snippet shows how to define a client interface
to handle a read method.
</p>
<macro name="snippet">
<param name="id" value="clientReadInterface" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<a name="instance_vread" /> <a name="instance_vread" />
</section> </section>
@ -210,7 +236,7 @@
<b>vread</b> <b>vread</b>
</a> </a>
operation retrieves a specific version of a resource with a given ID. It looks exactly operation retrieves a specific version of a resource with a given ID. It looks exactly
like a "read" operation, but with a second like a "read" operation, but with a second <code>IdDt</code>
parameter annotated with the parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/VersionIdParam.html">@VersionIdParam</a> <a href="./apidocs/ca/uhn/fhir/rest/annotation/VersionIdParam.html">@VersionIdParam</a>
annotation. annotation.
@ -227,6 +253,13 @@
<code>http://fhir.example.com/Patient/111/_history/2</code> <code>http://fhir.example.com/Patient/111/_history/2</code>
</p> </p>
<p>
To define a client method to handle a VRead method, the
syntax is the same as the <a href="#instance_vread">read</a> method
above, except that you will add a second <code>IdDt</code> parameter
annotated with the <code>@VersionIdParam</code> annotation.
</p>
<a name="instance_update" /> <a name="instance_update" />
</section> </section>
@ -465,7 +498,8 @@
<p> <p>
This annotation takes a "name" parameter which specifies the parameter's This annotation takes a "name" parameter which specifies the parameter's
name (as it will appear in the search URL). FHIR defines standardized parameter name (as it will appear in the search URL). FHIR defines standardized parameter
names for each resource, and these are available as constants on the names for each
resource, and these are available as constants on the
individual HAPI resource individual HAPI resource
classes. classes.
</p> </p>
@ -603,17 +637,26 @@
<p> <p>
Note that when using a date range parameter, it is also possible for Note that when using a date range parameter, it is also possible for
the client to request an "unbounded" range. In other words, a range that the client to request an "unbounded" range. In other words, a range that
only a start date and no end date, or vice versa. only a start date and no end
date, or vice versa.
</p> </p>
<p> <p>
An example of this might be the following URL, which refers to any Observation An example of this might be the following URL, which refers to any Observation
resources for the given MRN and having a date after 2011-01-01. resources for the given MRN and having a date after 2011-01-01.
<br/> <br />
<code>http://fhir.example.com/Observation?subject.identifier=7000135&amp;date=&gt;=2011-01-01</code><br/> <code>http://fhir.example.com/Observation?subject.identifier=7000135&amp;date=&gt;=2011-01-01</code>
<br />
When such a request is made of a server (or to make such a request from a client), When such a request is made of a server (or to make such a request from a client),
the <code>getLowerBound()</code> or <code>getUpperBound()</code> property of the the
<code>DateRangeParam</code> object will be set to <code>null</code>. <code>getLowerBound()</code>
or
<code>getUpperBound()</code>
property of the
<code>DateRangeParam</code>
object will be set to
<code>null</code>
.
</p> </p>
</subsection> </subsection>
@ -847,7 +890,8 @@
</li> </li>
<li> <li>
An object of type An object of type
<a href="./apidocs/ca/uhn/fhir/rest/api/MethodOutcome.html">MethodOutcome</a>. The <a href="./apidocs/ca/uhn/fhir/rest/api/MethodOutcome.html">MethodOutcome</a>
. The
MethodOutcome may optionally be populated with an OperationOutcome resource, which MethodOutcome may optionally be populated with an OperationOutcome resource, which
will be returned to the client if it exists. will be returned to the client if it exists.
</li> </li>
@ -987,38 +1031,54 @@
annotated with the annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a> <a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation, indicating the ID of the resource for which to return history. annotation, indicating the ID of the resource for which to return history.
<ul><li> <ul>
<li>
For a server For a server
implementation, the method must either be defined in a implementation, the method must either be defined in a
<a href="./doc_rest_server.html#resource_providers">resource provider</a> <a href="./doc_rest_server.html#resource_providers">resource provider</a>
or have a <code>type()</code> value in the @History annotation if it is or have a
<code>type()</code>
value in the @History annotation if it is
defined in a defined in a
<a href="./doc_rest_server.html#plain_providers">plain provider</a>. <a href="./doc_rest_server.html#plain_providers">plain provider</a>
</li></ul> .
</li>
</ul>
</li> </li>
<li> <li>
For a For a
<b>Type History</b> <b>Type History</b>
method, the method must not have any @IdParam parameter. method, the method must not have any @IdParam parameter.
<ul><li> <ul>
<li>
For a server For a server
implementation, the method must either be defined in a implementation, the method must either be defined in a
<a href="./doc_rest_server.html#resource_providers">resource provider</a> <a href="./doc_rest_server.html#resource_providers">resource provider</a>
or have a <code>type()</code> value in the @History annotation if it is or have a
<code>type()</code>
value in the @History annotation if it is
defined in a defined in a
<a href="./doc_rest_server.html#plain_providers">plain provider</a>. <a href="./doc_rest_server.html#plain_providers">plain provider</a>
</li></ul> .
</li>
</ul>
</li> </li>
<li> <li>
For a For a
<b>Server History</b> <b>Server History</b>
method, the method must not have any @IdParam parameter, and method, the method must not have any @IdParam parameter, and
must not have a <code>type()</code> value specified in must not have a
<code>type()</code>
value specified in
the @History annotation. the @History annotation.
<ul><li> <ul>
<li>
In a server implementation, the method must In a server implementation, the method must
be defined in a <a href="./doc_rest_server.html#plain_providers">plain provider</a>. be defined in a
</li></ul> <a href="./doc_rest_server.html#plain_providers">plain provider</a>
.
</li>
</ul>
</li> </li>
</ul> </ul>
<p> <p>
@ -1039,16 +1099,23 @@
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" /> <param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro> </macro>
<a name="exceptions"/> <a name="exceptions" />
</section> </section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Exceptions"> <section name="Exceptions">
<p> <p>
When implementing a server operation, there are a number of failure conditions When implementing a server operation, there are a number of failure conditions
specified. For example, an specified. For example, an
<a href="#instance_read">Instance Read</a> request might specify an unknown <a href="#instance_read">Instance Read</a>
resource ID, or a <a href="#type_create">Type Create</a> request might contain an request might specify an unknown
resource ID, or a
<a href="#type_create">Type Create</a>
request might contain an
invalid resource which can not be created. invalid resource which can not be created.
</p> </p>
<p> <p>
@ -1060,11 +1127,125 @@
<p> <p>
See the See the
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/package-summary.html">Exceptions List</a> <a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/package-summary.html">Exceptions List</a>
for a complete list of these exceptions. Note that these exceptions are all <b>unchecked</b> for a complete list of these exceptions. Note that these exceptions are all
<b>unchecked</b>
exceptions, so they do not need to ne explicitly declared in the method exceptions, so they do not need to ne explicitly declared in the method
signature. signature.
</p> </p>
<a name="tags" />
</section>
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<!-- ****************************************************************** -->
<section name="Tags">
<p>
FHIR RESTful servers may support a feature known as tagging. Tags are a set of
named flags called "terms" (with an optional accompanying human friendly name
called a "label",
and an optional namespace called a "scheme").
</p>
<p>
Tags have very specific semantics, which may not be
obvious simply by using the HAPI API. It is important to review the specification
pages
<a href="http://hl7.org/implement/standards/fhir/http.html#tags">here</a>
and
<a href="http://hl7.org/implement/standards/fhir/extras.html#tags">here</a>
before attempting to implement tagging in your own applications.
</p>
<subsection name="Accessing Tags in a Read / VRead / Search Method">
<p>
Tags are stored within a resource object, in the
<a href="./apidocs/ca/uhn/fhir/model/api/IResource.html#getResourceMetadata()">IResource.html#getResourceMetadata()</a>
map, under the key
<a href="./apidocs/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.html#TAG_LIST">TAG_LIST</a>.
</p>
<p>
In a server implementation, you may populate your tags into the
returned resource(s) and HAPI will automatically place these tags into
the response headers (for read/vread) or the bundle category tags (for search).
The following example illustrates how to return tags from a server method. This
example shows how to supply tags in a read method, but the same approach applies
to vread and search operations as well.
</p>
<macro name="snippet">
<param name="id" value="readTags" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
In a client operation, you simply call the read/vread/search method as you
normally would (as described above), and if any tags have been returned
by the server, these may be accessed from the resource metadata.
</p>
<macro name="snippet">
<param name="id" value="clientReadTags" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</subsection>
<subsection name="Setting Tags in a Create/Update Method">
<p>
Within a <a href="#type_create">Type Create</a>
or <a href="#instance_update">Instance Update</a> method, it is
possible for the client to specify a set of tags to be stored
along with the saved resource instance.
</p>
<p>
Note that FHIR specifies that in an update method, any tags supplied
by the client are copied to the newly saved version, as well as any
tags the existing version had.
</p>
<p>
To work with tags in a create/update method, the pattern used in the
read examples above is simply revered. In a server, the resource which
is passed in will be populated with any tags that the client supplied:
</p>
<macro name="snippet">
<param name="id" value="createTags" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</subsection>
<subsection name="More tag methods">
<p>
FHIR also provides a number of operations to interact directly
with tags. These methods may be used to retrieve lists of tags
that are available on the server, or to add or remove tags from
resources without interacting directly with those resources.
</p>
<p>
On a server these methods may be placed in a plain provider, or in a resource
provider in the case of resource type specific methods.
</p>
<macro name="snippet">
<param name="id" value="tagMethodProvider" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
On a client, the methods are defined in the exact same way, except that
there is no method body in the client interface.
</p>
</subsection>
</section> </section>
</body> </body>

View File

@ -100,7 +100,7 @@ public class ResourceWithExtensionsA extends BaseResource {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return false; // TODO: implement return false; // not implemented
} }
@Override @Override
@ -110,7 +110,7 @@ public class ResourceWithExtensionsA extends BaseResource {
@Override @Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) { public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // TODO: implement return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
} }
@ -159,7 +159,7 @@ public class ResourceWithExtensionsA extends BaseResource {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return false; // TODO: implement return false; // not implemented
} }
@Override @Override
@ -169,7 +169,7 @@ public class ResourceWithExtensionsA extends BaseResource {
@Override @Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) { public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // TODO: implement return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
} }
@ -204,7 +204,7 @@ public class ResourceWithExtensionsA extends BaseResource {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return false; // TODO: implement return false; // not implemented
} }
@Override @Override
@ -214,7 +214,7 @@ public class ResourceWithExtensionsA extends BaseResource {
@Override @Override
public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) { public <T extends IElement> List<T> getAllPopulatedChildElementsOfType(Class<T> theType) {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // TODO: implement return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // not implemented
} }

View File

@ -2,7 +2,6 @@ package ca.uhn.fhir.narrative;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.Date; import java.util.Date;
@ -77,7 +76,7 @@ public class DefaultThymeleafNarrativeGeneratorTest {
} }
@Test @Test
public void testGenerateDiagnosticReportWithObservations() throws DataFormatException, IOException { public void testGenerateDiagnosticReportWithObservations() throws DataFormatException {
DiagnosticReport value = new DiagnosticReport(); DiagnosticReport value = new DiagnosticReport();
value.getIssued().setValueAsString("2011-02-22T11:13:00"); value.getIssued().setValueAsString("2011-02-22T11:13:00");

View File

@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Extension; import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.api.annotation.ResourceDef;
@ -50,17 +51,71 @@ import ca.uhn.fhir.narrative.INarrativeGenerator;
public class JsonParserTest { public class JsonParserTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParserTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JsonParserTest.class);
/**
* This sample has extra elements in <searchParam> that are not actually a part of the spec any more..
*/
@Test @Test
public void testParseFuroreMetadataWithExtraElements() throws IOException { public void testTagList() {
String msg = IOUtils.toString(JsonParserTest.class.getResourceAsStream("/furore-conformance.json"));
//@formatter:off
String tagListStr = "{\n" +
" \"resourceType\" : \"TagList\", " +
" \"category\" : [" +
" { " +
" \"term\" : \"term0\", " +
" \"label\" : \"label0\", " +
" \"scheme\" : \"scheme0\" " +
" }," +
" { " +
" \"term\" : \"term1\", " +
" \"label\" : \"label1\", " +
" \"scheme\" : null " +
" }," +
" { " +
" \"term\" : \"term2\", " +
" \"label\" : \"label2\" " +
" }" +
" ] " +
"}";
//@formatter:on
TagList tagList = new FhirContext().newJsonParser().parseTagList(tagListStr);
assertEquals(3, tagList.size());
assertEquals("term0", tagList.get(0).getTerm());
assertEquals("label0", tagList.get(0).getLabel());
assertEquals("scheme0", tagList.get(0).getScheme());
assertEquals("term1", tagList.get(1).getTerm());
assertEquals("label1", tagList.get(1).getLabel());
assertEquals(null, tagList.get(1).getScheme());
assertEquals("term2", tagList.get(2).getTerm());
assertEquals("label2", tagList.get(2).getLabel());
assertEquals(null, tagList.get(2).getScheme());
/*
* Encode
*/
//@formatter:off
String expected = "{" +
"\"resourceType\":\"TagList\"," +
"\"category\":[" +
"{" +
"\"term\":\"term0\"," +
"\"label\":\"label0\"," +
"\"scheme\":\"scheme0\"" +
"}," +
"{" +
"\"term\":\"term1\"," +
"\"label\":\"label1\"" +
"}," +
"{" +
"\"term\":\"term2\"," +
"\"label\":\"label2\"" +
"}" +
"]" +
"}";
//@formatter:on
String encoded = new FhirContext().newJsonParser().encodeTagListToString(tagList);
assertEquals(expected,encoded);
IParser p = new FhirContext(ValueSet.class).newJsonParser();
Conformance conf = p.parseResource(Conformance.class, msg);
RestResource res = conf.getRestFirstRep().getResourceFirstRep();
assertEquals("_id", res.getSearchParam().get(1).getName().getValue());
} }
@Test @Test
@ -88,368 +143,6 @@ public class JsonParserTest {
} }
@Test
public void testBrokenPatient() {
String resource = "{\n" +
" \"resourceType\": \"Patient\",\n" +
" \"extension\": [\n" +
" {\n" +
" \"url\": \"http://mayoweb.mayo.edu/dms-it/reference/data_standards/reg-ctable-race.html#curr_race\",\n" +
" \"valueCodeableConcept\": {\n" +
" \"coding\": [\n" +
" {\n" +
" \"system\": \"http://mayoweb.mayo.edu/dms-it/reference/data_standards/reg-ctable-race.html#curr_race\",\n" +
" \"code\": \"O\",\n" +
" \"display\": \"Other\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" },\n" +
" {\n" +
" \"url\": \"http://mayoweb.mayo.edu/dms-it/reference/data_standards/reg-ctable-race.html#curr_ethn\",\n" +
" \"valueCodeableConcept\": {\n" +
" \"coding\": [\n" +
" {\n" +
" \"system\": \"http://mayoweb.mayo.edu/dms-it/reference/data_standards/reg-ctable-race.html#curr_ethn\",\n" +
" \"code\": \"HA\",\n" +
" \"display\": \"Central American\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"identifier\": [\n" +
" {\n" +
" \"label\": \"MAYO-CLINIC-NUMBER\",\n" +
" \"value\": \"3982540\"\n" +
" }\n" +
" ],\n" +
" \"name\": [\n" +
" {\n" +
" \"use\": \"usual\",\n" +
" \"text\": \"Hurt, Mr. John Stevens\",\n" +
" \"family\": [\n" +
" \"Hurt\"\n" +
" ],\n" +
" \"given\": [\n" +
" \"John Stevens\"\n" +
" ],\n" +
" \"prefix\": [\n" +
" \"Mr.\"\n" +
" ],\n" +
" \"suffix\": [\n" +
" null\n" +
" ]\n" +
" }\n" +
" ],\n" +
" \"telecom\": [\n" +
" {\n" +
" \"system\": \"phone\",\n" +
" \"value\": \"(507)284-0122\"\n" +
" }\n" +
" ],\n" +
" \"gender\": {\n" +
" \"coding\": [\n" +
" {\n" +
" \"system\": \"http://hl7.org/fhir/v3/AdministrativeGender\",\n" +
" \"code\": \"M\",\n" +
" \"display\": \"MALE\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"birthDate\": \"1986-05-01T00:00:00-05:00\",\n" +
" \"deceasedDateTime\": \"0001-01-01T00:00:00-06:00\",\n" +
" \"address\": [\n" +
" {\n" +
" \"use\": \"home\",\n" +
" \"line\": [\n" +
" \"100 Quality Control Avenue\"\n" +
" ],\n" +
" \"city\": \"Test Patient\",\n" +
" \"state\": \"MINNESOTA\",\n" +
" \"zip\": \"55905\",\n" +
" \"country\": \"USA\"\n" +
" }\n" +
" ],\n" +
" \"maritalStatus\": {\n" +
" \"coding\": [\n" +
" {\n" +
" \"system\": \"http://hl7.org/fhir/v3/MaritalStatus\",\n" +
" \"code\": \"M\",\n" +
" \"display\": \"Married\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"communication\": [\n" +
" {\n" +
" \"coding\": [\n" +
" {\n" +
" \"code\": \"ENG\",\n" +
" \"display\": \"ENGLISH\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}";
FhirContext ctx = new FhirContext();
Patient patient = ctx.newJsonParser().parseResource(Patient.class, resource);
System.out.println(patient.getNameFirstRep().getFamilyAsSingleString());
}
@Test
public void testEncodeExtensionWithResourceContent() throws IOException {
IParser parser = new FhirContext().newJsonParser();
Patient patient = new Patient();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.addUndeclaredExtension(false, "urn:foo", new ResourceReferenceDt(Organization.class, "123"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]"));
Patient actual = parser.parseResource(Patient.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
List<ExtensionDt> ext = actual.getUndeclaredExtensionsByUrl("urn:foo");
assertEquals(1, ext.size());
ResourceReferenceDt ref = (ResourceReferenceDt) ext.get(0).getValue();
assertEquals("Organization/123", ref.getResourceUrl());
}
@Test
public void testEncodeDeclaredExtensionWithResourceContent() throws IOException {
IParser parser = new FhirContext().newJsonParser();
MyPatientWithOneDeclaredExtension patient = new MyPatientWithOneDeclaredExtension();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.setFoo(new ResourceReferenceDt(Organization.class, "123"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]"));
MyPatientWithOneDeclaredExtension actual = parser.parseResource(MyPatientWithOneDeclaredExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
ResourceReferenceDt ref = actual.getFoo();
assertEquals("Organization/123", ref.getResourceUrl());
}
@Test
public void testEncodeDeclaredExtensionWithAddressContent() throws IOException {
IParser parser = new FhirContext().newJsonParser();
MyPatientWithOneDeclaredAddressExtension patient = new MyPatientWithOneDeclaredAddressExtension();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.setFoo(new AddressDt().addLine("line1"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueAddress\":{\"line\":[\"line1\"]}}]"));
MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
AddressDt ref = actual.getFoo();
assertEquals("line1", ref.getLineFirstRep().getValue());
}
@Test
public void testEncodeUndeclaredExtensionWithAddressContent() throws IOException {
IParser parser = new FhirContext().newJsonParser();
Patient patient = new Patient();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.addUndeclaredExtension(false, "urn:foo", new AddressDt().addLine("line1"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueAddress\":{\"line\":[\"line1\"]}}]"));
MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
AddressDt ref = actual.getFoo();
assertEquals("line1", ref.getLineFirstRep().getValue());
}
@ResourceDef(name = "Patient")
public static class MyPatientWithOneDeclaredExtension extends Patient {
@Child(order = 0, name = "foo")
@Extension(url = "urn:foo", definedLocally = true, isModifier = false)
private ResourceReferenceDt myFoo;
public ResourceReferenceDt getFoo() {
return myFoo;
}
public void setFoo(ResourceReferenceDt theFoo) {
myFoo = theFoo;
}
}
@ResourceDef(name = "Patient")
public static class MyPatientWithOneDeclaredAddressExtension extends Patient {
@Child(order = 0, name = "foo")
@Extension(url = "urn:foo", definedLocally = true, isModifier = false)
private AddressDt myFoo;
public AddressDt getFoo() {
return myFoo;
}
public void setFoo(AddressDt theFoo) {
myFoo = theFoo;
}
}
@Test
public void testEncodeExt() throws Exception {
ValueSet valueSet = new ValueSet();
Define define = valueSet.getDefine();
DefineConcept code = define.addConcept();
code.setCode("someCode");
code.setDisplay("someDisplay");
code.addUndeclaredExtension(false, "urn:alt", new StringDt("alt name"));
String encoded = new FhirContext().newJsonParser().encodeResourceToString(valueSet);
ourLog.info(encoded);
}
@Test
public void testEncodeResourceRef() throws DataFormatException, IOException {
Patient patient = new Patient();
patient.setManagingOrganization(new ResourceReferenceDt());
IParser p = new FhirContext().newJsonParser();
String str = p.encodeResourceToString(patient);
assertThat(str, IsNot.not(StringContains.containsString("managingOrganization")));
patient.setManagingOrganization(new ResourceReferenceDt(Organization.class, "123"));
str = p.encodeResourceToString(patient);
assertThat(str, StringContains.containsString("\"managingOrganization\":{\"resource\":\"Organization/123\"}"));
Organization org = new Organization();
org.addIdentifier().setSystem("foo").setValue("bar");
patient.setManagingOrganization(new ResourceReferenceDt(org));
str = p.encodeResourceToString(patient);
assertThat(str, StringContains.containsString("\"contained\":[{\"resourceType\":\"Organization\""));
}
@Test
public void testNarrativeGeneration() throws DataFormatException, IOException {
Patient patient = new Patient();
patient.addName().addFamily("Smith");
Organization org = new Organization();
patient.getManagingOrganization().setResource(org);
INarrativeGenerator gen = mock(INarrativeGenerator.class);
XhtmlDt xhtmlDt = new XhtmlDt("<div>help</div>");
NarrativeDt nar = new NarrativeDt(xhtmlDt, NarrativeStatusEnum.GENERATED);
when(gen.generateNarrative(eq("http://hl7.org/fhir/profiles/Patient"), eq(patient))).thenReturn(nar);
FhirContext context = new FhirContext();
context.setNarrativeGenerator(gen);
IParser p = context.newJsonParser();
p.encodeResourceToWriter(patient, new OutputStreamWriter(System.out));
String str = p.encodeResourceToString(patient);
ourLog.info(str);
assertThat(str, StringContains.containsString(",\"text\":{\"status\":\"generated\",\"div\":\"<div>help</div>\"},"));
}
@Test
public void testSimpleResourceEncode() throws IOException {
FhirContext ctx = new FhirContext(Observation.class);
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.xml"), Charset.forName("UTF-8"));
Patient obs = ctx.newXmlParser().parseResource(Patient.class, xmlString);
List<ExtensionDt> undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions();
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl().getValue());
ctx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
IParser jsonParser = ctx.newJsonParser();
String encoded = jsonParser.encodeResourceToString(obs);
ourLog.info(encoded);
String jsonString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.json"));
JSON expected = JSONSerializer.toJSON(jsonString);
JSON actual = JSONSerializer.toJSON(encoded.trim());
ourLog.info("Expected: {}", expected);
ourLog.info("Actual : {}", actual);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void testSimpleResourceEncodeWithCustomType() throws IOException {
FhirContext ctx = new FhirContext(MyObservationWithExtensions.class);
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.xml"), Charset.forName("UTF-8"));
MyObservationWithExtensions obs = ctx.newXmlParser().parseResource(MyObservationWithExtensions.class, xmlString);
assertEquals(0, obs.getAllUndeclaredExtensions().size());
assertEquals("aaaa", obs.getExtAtt().getContentType().getValue());
assertEquals("str1", obs.getMoreExt().getStr1().getValue());
assertEquals("2011-01-02", obs.getModExt().getValueAsString());
List<ExtensionDt> undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions();
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl().getValue());
ctx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
IParser jsonParser = ctx.newJsonParser();
String encoded = jsonParser.encodeResourceToString(obs);
ourLog.info(encoded);
String jsonString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.json"));
JSON expected = JSONSerializer.toJSON(jsonString);
JSON actual = JSONSerializer.toJSON(encoded.trim());
ourLog.info("Expected: {}", expected);
ourLog.info("Actual : {}", actual);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void testSimpleBundleEncode() throws IOException {
FhirContext ctx = new FhirContext(Observation.class, Patient.class);
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/atom-document-large.xml"), Charset.forName("UTF-8"));
Bundle obs = ctx.newXmlParser().parseBundle(xmlString);
String encoded = ctx.newJsonParser().encodeBundleToString(obs);
ourLog.info(encoded);
}
@Test @Test
public void testEncodeContainedResources() throws IOException { public void testEncodeContainedResources() throws IOException {
@ -463,68 +156,9 @@ public class JsonParserTest {
} }
@Test
public void testSimpleParse() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/example-patient-general.json"));
FhirContext ctx = new FhirContext(Patient.class);
IParser p = ctx.newJsonParser();
// ourLog.info("Reading in message: {}", msg);
Patient res = p.parseResource(Patient.class, msg);
assertEquals(2, res.getUndeclaredExtensions().size());
assertEquals(1, res.getUndeclaredModifierExtensions().size());
String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res);
ourLog.info(encoded);
}
@Test @Test
public void testParseBundle() throws DataFormatException, IOException { public void testEncodeContainedResourcesMore() {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/atom-document-large.json"));
FhirContext ctx = new FhirContext(Patient.class);
IParser p = ctx.newJsonParser();
Bundle bundle = p.parseBundle(msg);
String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle);
ourLog.info(encoded);
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/_search?_format=application/json+fhir&search-id=46d5f0e7-9240-4d4f-9f51-f8ac975c65&search-sort=_id", bundle
.getLinkSelf().getValue());
assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue());
BundleEntry entry = bundle.getEntries().get(0);
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101", entry.getId().getValue());
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101/_history/1", entry.getLinkSelf().getValue());
assertEquals("2014-03-10T11:55:59Z", entry.getUpdated().getValueAsString());
DiagnosticReport res = (DiagnosticReport) entry.getResource();
assertEquals("Complete Blood Count", res.getName().getText().getValue());
}
@Test
public void testParseWithContained() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/diagnostic-report.json"));
FhirContext ctx = new FhirContext(Patient.class);
IParser p = ctx.newJsonParser();
// ourLog.info("Reading in message: {}", msg);
DiagnosticReport res = p.parseResource(DiagnosticReport.class, msg);
String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res);
ourLog.info(encoded);
ResourceReferenceDt reference = res.getResult().get(1);
Observation obs = (Observation) reference.getResource();
assertEquals("789-8", obs.getName().getCoding().get(0).getCode().getValue());
}
@Test
public void testEncodeContainedResourcesMore() throws IOException {
DiagnosticReport rpt = new DiagnosticReport(); DiagnosticReport rpt = new DiagnosticReport();
Specimen spm = new Specimen(); Specimen spm = new Specimen();
@ -548,7 +182,85 @@ public class JsonParserTest {
} }
@Test @Test
public void testEncodeInvalidChildGoodException() throws IOException { public void testEncodeDeclaredExtensionWithAddressContent() {
IParser parser = new FhirContext().newJsonParser();
MyPatientWithOneDeclaredAddressExtension patient = new MyPatientWithOneDeclaredAddressExtension();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.setFoo(new AddressDt().addLine("line1"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueAddress\":{\"line\":[\"line1\"]}}]"));
MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
AddressDt ref = actual.getFoo();
assertEquals("line1", ref.getLineFirstRep().getValue());
}
@Test
public void testEncodeDeclaredExtensionWithResourceContent() {
IParser parser = new FhirContext().newJsonParser();
MyPatientWithOneDeclaredExtension patient = new MyPatientWithOneDeclaredExtension();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.setFoo(new ResourceReferenceDt(Organization.class, "123"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]"));
MyPatientWithOneDeclaredExtension actual = parser.parseResource(MyPatientWithOneDeclaredExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
ResourceReferenceDt ref = actual.getFoo();
assertEquals("Organization/123", ref.getResourceUrl());
}
@Test
public void testEncodeExt() throws Exception {
ValueSet valueSet = new ValueSet();
Define define = valueSet.getDefine();
DefineConcept code = define.addConcept();
code.setCode("someCode");
code.setDisplay("someDisplay");
code.addUndeclaredExtension(false, "urn:alt", new StringDt("alt name"));
String encoded = new FhirContext().newJsonParser().encodeResourceToString(valueSet);
ourLog.info(encoded);
}
@Test
public void testEncodeExtensionWithResourceContent() {
IParser parser = new FhirContext().newJsonParser();
Patient patient = new Patient();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.addUndeclaredExtension(false, "urn:foo", new ResourceReferenceDt(Organization.class, "123"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueResource\":{\"resource\":\"Organization/123\"}}]"));
Patient actual = parser.parseResource(Patient.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
List<ExtensionDt> ext = actual.getUndeclaredExtensionsByUrl("urn:foo");
assertEquals(1, ext.size());
ResourceReferenceDt ref = (ResourceReferenceDt) ext.get(0).getValue();
assertEquals("Organization/123", ref.getResourceUrl());
}
@Test
public void testEncodeInvalidChildGoodException() {
Observation obs = new Observation(); Observation obs = new Observation();
obs.setValue(new DecimalDt(112.22)); obs.setValue(new DecimalDt(112.22));
@ -561,6 +273,47 @@ public class JsonParserTest {
} }
} }
@Test
public void testEncodeResourceRef() throws DataFormatException {
Patient patient = new Patient();
patient.setManagingOrganization(new ResourceReferenceDt());
IParser p = new FhirContext().newJsonParser();
String str = p.encodeResourceToString(patient);
assertThat(str, IsNot.not(StringContains.containsString("managingOrganization")));
patient.setManagingOrganization(new ResourceReferenceDt(Organization.class, "123"));
str = p.encodeResourceToString(patient);
assertThat(str, StringContains.containsString("\"managingOrganization\":{\"resource\":\"Organization/123\"}"));
Organization org = new Organization();
org.addIdentifier().setSystem("foo").setValue("bar");
patient.setManagingOrganization(new ResourceReferenceDt(org));
str = p.encodeResourceToString(patient);
assertThat(str, StringContains.containsString("\"contained\":[{\"resourceType\":\"Organization\""));
}
@Test
public void testEncodeUndeclaredExtensionWithAddressContent() {
IParser parser = new FhirContext().newJsonParser();
Patient patient = new Patient();
patient.addAddress().setUse(AddressUseEnum.HOME);
patient.addUndeclaredExtension(false, "urn:foo", new AddressDt().addLine("line1"));
String val = parser.encodeResourceToString(patient);
ourLog.info(val);
assertThat(val, StringContains.containsString("\"extension\":[{\"url\":\"urn:foo\",\"valueAddress\":{\"line\":[\"line1\"]}}]"));
MyPatientWithOneDeclaredAddressExtension actual = parser.parseResource(MyPatientWithOneDeclaredAddressExtension.class, val);
assertEquals(AddressUseEnum.HOME, patient.getAddressFirstRep().getUse().getValueAsEnum());
AddressDt ref = actual.getFoo();
assertEquals("line1", ref.getLineFirstRep().getValue());
}
@Test @Test
public void testExtensionOnComposite() throws Exception { public void testExtensionOnComposite() throws Exception {
@ -654,4 +407,208 @@ public class JsonParserTest {
} }
@Test
public void testNarrativeGeneration() throws DataFormatException, IOException {
Patient patient = new Patient();
patient.addName().addFamily("Smith");
Organization org = new Organization();
patient.getManagingOrganization().setResource(org);
INarrativeGenerator gen = mock(INarrativeGenerator.class);
XhtmlDt xhtmlDt = new XhtmlDt("<div>help</div>");
NarrativeDt nar = new NarrativeDt(xhtmlDt, NarrativeStatusEnum.GENERATED);
when(gen.generateNarrative(eq("http://hl7.org/fhir/profiles/Patient"), eq(patient))).thenReturn(nar);
FhirContext context = new FhirContext();
context.setNarrativeGenerator(gen);
IParser p = context.newJsonParser();
p.encodeResourceToWriter(patient, new OutputStreamWriter(System.out));
String str = p.encodeResourceToString(patient);
ourLog.info(str);
assertThat(str, StringContains.containsString(",\"text\":{\"status\":\"generated\",\"div\":\"<div>help</div>\"},"));
}
@Test
public void testParseBundle() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/atom-document-large.json"));
FhirContext ctx = new FhirContext(Patient.class);
IParser p = ctx.newJsonParser();
Bundle bundle = p.parseBundle(msg);
String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeBundleToString(bundle);
ourLog.info(encoded);
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/_search?_format=application/json+fhir&search-id=46d5f0e7-9240-4d4f-9f51-f8ac975c65&search-sort=_id", bundle
.getLinkSelf().getValue());
assertEquals("urn:uuid:0b754ff9-03cf-4322-a119-15019af8a3", bundle.getBundleId().getValue());
BundleEntry entry = bundle.getEntries().get(0);
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101", entry.getId().getValue());
assertEquals("http://fhir.healthintersections.com.au/open/DiagnosticReport/101/_history/1", entry.getLinkSelf().getValue());
assertEquals("2014-03-10T11:55:59Z", entry.getUpdated().getValueAsString());
DiagnosticReport res = (DiagnosticReport) entry.getResource();
assertEquals("Complete Blood Count", res.getName().getText().getValue());
}
/**
* This sample has extra elements in <searchParam> that are not actually a part of the spec any more..
*/
@Test
public void testParseFuroreMetadataWithExtraElements() throws IOException {
String msg = IOUtils.toString(JsonParserTest.class.getResourceAsStream("/furore-conformance.json"));
IParser p = new FhirContext(ValueSet.class).newJsonParser();
Conformance conf = p.parseResource(Conformance.class, msg);
RestResource res = conf.getRestFirstRep().getResourceFirstRep();
assertEquals("_id", res.getSearchParam().get(1).getName().getValue());
}
@Test
public void testParseWithContained() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/diagnostic-report.json"));
FhirContext ctx = new FhirContext(Patient.class);
IParser p = ctx.newJsonParser();
// ourLog.info("Reading in message: {}", msg);
DiagnosticReport res = p.parseResource(DiagnosticReport.class, msg);
String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res);
ourLog.info(encoded);
ResourceReferenceDt reference = res.getResult().get(1);
Observation obs = (Observation) reference.getResource();
assertEquals("789-8", obs.getName().getCoding().get(0).getCode().getValue());
}
@Test
public void testSimpleBundleEncode() throws IOException {
FhirContext ctx = new FhirContext(Observation.class, Patient.class);
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/atom-document-large.xml"), Charset.forName("UTF-8"));
Bundle obs = ctx.newXmlParser().parseBundle(xmlString);
String encoded = ctx.newJsonParser().encodeBundleToString(obs);
ourLog.info(encoded);
}
@Test
public void testSimpleParse() throws DataFormatException, IOException {
String msg = IOUtils.toString(XmlParser.class.getResourceAsStream("/example-patient-general.json"));
FhirContext ctx = new FhirContext(Patient.class);
IParser p = ctx.newJsonParser();
// ourLog.info("Reading in message: {}", msg);
Patient res = p.parseResource(Patient.class, msg);
assertEquals(2, res.getUndeclaredExtensions().size());
assertEquals(1, res.getUndeclaredModifierExtensions().size());
String encoded = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(res);
ourLog.info(encoded);
}
@Test
public void testSimpleResourceEncode() throws IOException {
FhirContext ctx = new FhirContext(Observation.class);
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.xml"), Charset.forName("UTF-8"));
Patient obs = ctx.newXmlParser().parseResource(Patient.class, xmlString);
List<ExtensionDt> undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions();
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl().getValue());
ctx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
IParser jsonParser = ctx.newJsonParser();
String encoded = jsonParser.encodeResourceToString(obs);
ourLog.info(encoded);
String jsonString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.json"));
JSON expected = JSONSerializer.toJSON(jsonString);
JSON actual = JSONSerializer.toJSON(encoded.trim());
ourLog.info("Expected: {}", expected);
ourLog.info("Actual : {}", actual);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void testSimpleResourceEncodeWithCustomType() throws IOException {
FhirContext ctx = new FhirContext(MyObservationWithExtensions.class);
String xmlString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.xml"), Charset.forName("UTF-8"));
MyObservationWithExtensions obs = ctx.newXmlParser().parseResource(MyObservationWithExtensions.class, xmlString);
assertEquals(0, obs.getAllUndeclaredExtensions().size());
assertEquals("aaaa", obs.getExtAtt().getContentType().getValue());
assertEquals("str1", obs.getMoreExt().getStr1().getValue());
assertEquals("2011-01-02", obs.getModExt().getValueAsString());
List<ExtensionDt> undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions();
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl().getValue());
ctx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
IParser jsonParser = ctx.newJsonParser();
String encoded = jsonParser.encodeResourceToString(obs);
ourLog.info(encoded);
String jsonString = IOUtils.toString(JsonParser.class.getResourceAsStream("/example-patient-general.json"));
JSON expected = JSONSerializer.toJSON(jsonString);
JSON actual = JSONSerializer.toJSON(encoded.trim());
ourLog.info("Expected: {}", expected);
ourLog.info("Actual : {}", actual);
assertEquals(expected.toString(), actual.toString());
}
@ResourceDef(name = "Patient")
public static class MyPatientWithOneDeclaredAddressExtension extends Patient {
@Child(order = 0, name = "foo")
@Extension(url = "urn:foo", definedLocally = true, isModifier = false)
private AddressDt myFoo;
public AddressDt getFoo() {
return myFoo;
}
public void setFoo(AddressDt theFoo) {
myFoo = theFoo;
}
}
@ResourceDef(name = "Patient")
public static class MyPatientWithOneDeclaredExtension extends Patient {
@Child(order = 0, name = "foo")
@Extension(url = "urn:foo", definedLocally = true, isModifier = false)
private ResourceReferenceDt myFoo;
public ResourceReferenceDt getFoo() {
return myFoo;
}
public void setFoo(ResourceReferenceDt theFoo) {
myFoo = theFoo;
}
}
} }

View File

@ -31,6 +31,8 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt; import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.composite.AddressDt; import ca.uhn.fhir.model.dstu.composite.AddressDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
@ -49,6 +51,7 @@ import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum; import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.model.primitive.DecimalDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.model.primitive.XhtmlDt;
import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.narrative.INarrativeGenerator;
@ -72,6 +75,47 @@ public class XmlParserTest {
} }
@Test
public void testTagList() {
//@formatter:off
String tagListStr = "<taglist xmlns=\"http://hl7.org/fhir\"> \n" +
" <category term=\"term0\" label=\"label0\" scheme=\"scheme0\" /> \n" +
" <category term=\"term1\" label=\"label1\" scheme=\"\" /> \n" +
" <category term=\"term2\" label=\"label2\" /> \n" +
"</taglist>";
//@formatter:on
TagList tagList = new FhirContext().newXmlParser().parseTagList(tagListStr);
assertEquals(3, tagList.size());
assertEquals("term0", tagList.get(0).getTerm());
assertEquals("label0", tagList.get(0).getLabel());
assertEquals("scheme0", tagList.get(0).getScheme());
assertEquals("term1", tagList.get(1).getTerm());
assertEquals("label1", tagList.get(1).getLabel());
assertEquals(null, tagList.get(1).getScheme());
assertEquals("term2", tagList.get(2).getTerm());
assertEquals("label2", tagList.get(2).getLabel());
assertEquals(null, tagList.get(2).getScheme());
/*
* Encode
*/
//@formatter:off
String expected = "<taglist xmlns=\"http://hl7.org/fhir\">" +
"<category term=\"term0\" label=\"label0\" scheme=\"scheme0\"/>" +
"<category term=\"term1\" label=\"label1\"/>" +
"<category term=\"term2\" label=\"label2\"/>" +
"</taglist>";
//@formatter:on
String encoded = new FhirContext().newXmlParser().encodeTagListToString(tagList);
assertEquals(expected,encoded);
}
@Test @Test
public void testTotalResultsUsingOldNamespace() { public void testTotalResultsUsingOldNamespace() {
@ -542,6 +586,7 @@ public class XmlParserTest {
" <uri>http://hl7.org/fhir</uri>\n" + " <uri>http://hl7.org/fhir</uri>\n" +
" </author>\n" + " </author>\n" +
" <published>2014-02-10T04:10:46.987-00:00</published>\n" + " <published>2014-02-10T04:10:46.987-00:00</published>\n" +
" <category term=\"term\" label=\"label\" scheme=\"http://foo\"/>\n "+
" <content type=\"text/xml\">\n" + " <content type=\"text/xml\">\n" +
" <ValueSet xmlns=\"http://hl7.org/fhir\">\n" + " <ValueSet xmlns=\"http://hl7.org/fhir\">\n" +
" <text>\n" + " <text>\n" +
@ -589,10 +634,29 @@ public class XmlParserTest {
BundleEntry entry = bundle.getEntries().get(0); BundleEntry entry = bundle.getEntries().get(0);
assertEquals("HL7, Inc (FHIR Project)", entry.getAuthorName().getValue()); assertEquals("HL7, Inc (FHIR Project)", entry.getAuthorName().getValue());
assertEquals("http://hl7.org/fhir/valueset/256a5231-a2bb-49bd-9fea-f349d428b70d", entry.getId().getValue()); assertEquals("http://hl7.org/fhir/valueset/256a5231-a2bb-49bd-9fea-f349d428b70d", entry.getId().getValue());
assertEquals(1, entry.getCategories().size());
assertEquals("term", entry.getCategories().get(0).getTerm());
assertEquals("label", entry.getCategories().get(0).getLabel());
assertEquals("http://foo", entry.getCategories().get(0).getScheme());
ValueSet resource = (ValueSet) entry.getResource(); ValueSet resource = (ValueSet) entry.getResource();
assertEquals("LOINC Codes for Cholesterol", resource.getName().getValue()); assertEquals("LOINC Codes for Cholesterol", resource.getName().getValue());
assertEquals(summaryText.trim(), entry.getSummary().getValueAsString().trim()); assertEquals(summaryText.trim(), entry.getSummary().getValueAsString().trim());
TagList tl = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
assertEquals(1, tl.size());
assertEquals("term", tl.get(0).getTerm());
assertEquals("label", tl.get(0).getLabel());
assertEquals("http://foo", tl.get(0).getScheme());
assertEquals(new IdDt("256a5231-a2bb-49bd-9fea-f349d428b70d"), resource.getId());
msg = msg.replace("<link href=\"http://hl7.org/implement/standards/fhir/valueset/256a5231-a2bb-49bd-9fea-f349d428b70d\" rel=\"self\"/>", "<link href=\"http://hl7.org/implement/standards/fhir/valueset/256a5231-a2bb-49bd-9fea-f349d428b70d/_history/12345\" rel=\"self\"/>");
entry = p.parseBundle(msg).getEntries().get(0);
resource = (ValueSet) entry.getResource();
assertEquals(new IdDt("256a5231-a2bb-49bd-9fea-f349d428b70d"), resource.getId());
assertEquals(new IdDt("12345"), resource.getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION_ID));
} }
@Test @Test

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.client; package ca.uhn.fhir.rest.client;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -49,12 +50,9 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt; import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.api.IBasicClient;
@ -64,6 +62,7 @@ import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
public class ClientTest { public class ClientTest {
@ -107,11 +106,29 @@ public class ClientTest {
assertEquals("200", response.getVersionId().getValue()); assertEquals("200", response.getVersionId().getValue());
} }
@Test
public void testCreateBad() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, "foobar"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8")));
try {
ctx.newRestfulClient(ITestClient.class, "http://foo").createPatient(patient);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), StringContains.containsString("foobar"));
}
}
/** /**
* Some servers (older ones?) return the resourcde you created instead * Some servers (older ones?) return the resourcde you created instead of an
* of an OperationOutcome. We just need to ignore it. * OperationOutcome. We just need to ignore it.
*/ */
@Test @Test
public void testCreateWithResourceResponse() throws Exception { public void testCreateWithResourceResponse() throws Exception {
@ -136,13 +153,6 @@ public class ClientTest {
assertEquals("200", response.getVersionId().getValue()); assertEquals("200", response.getVersionId().getValue());
} }
public interface CreateWithTagList extends IBasicClient
{
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient, TagList theTagList);
}
@Test @Test
public void testCreateWithTagList() throws Exception { public void testCreateWithTagList() throws Exception {
@ -156,11 +166,13 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200")); when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
CreateWithTagList client = ctx.newRestfulClient(CreateWithTagList.class, "http://foo"); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
TagList tagList = new TagList(); TagList tagList = new TagList();
tagList.add(new Tag("Dog", "DogLabel", (String)null)); tagList.add(new Tag("Dog", "DogLabel", (String) null));
tagList.add(new Tag("Cat", "CatLabel", "http://cats")); tagList.add(new Tag("Cat", "CatLabel", "http://cats"));
MethodOutcome response = client.createPatient(patient, tagList); patient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
MethodOutcome response = client.createPatient(patient);
assertEquals(HttpPost.class, capt.getValue().getClass()); assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue(); HttpPost post = (HttpPost) capt.getValue();
@ -175,27 +187,6 @@ public class ClientTest {
} }
@Test
public void testCreateBad() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, "foobar"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8")));
try {
ctx.newRestfulClient(ITestClient.class, "http://foo").createPatient(patient);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), StringContains.containsString("foobar"));
}
}
@Test @Test
public void testDelete() throws Exception { public void testDelete() throws Exception {
@ -398,7 +389,6 @@ public class ClientTest {
} }
} }
@Test @Test
public void testHistoryServer() throws Exception { public void testHistoryServer() throws Exception {
InstantDt date1 = new InstantDt(new Date(20000L)); InstantDt date1 = new InstantDt(new Date(20000L));
@ -489,16 +479,18 @@ public class ClientTest {
} }
}); });
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt(12)); client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02&_count=12", capt.getAllValues().get(0).getURI().toString()); assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02&_count=12", capt.getAllValues().get(0).getURI().toString());
String expectedDateString= new InstantDt(new InstantDt("2012-01-02T00:01:02").getValue()).getValueAsString(); // ensures the local timezone String expectedDateString = new InstantDt(new InstantDt("2012-01-02T00:01:02").getValue()).getValueAsString(); // ensures
expectedDateString=expectedDateString.replace(":", "%3A"); // the
// local
// timezone
expectedDateString = expectedDateString.replace(":", "%3A");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02").getValue(), new IntegerDt(12).getValue()); client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02").getValue(), new IntegerDt(12).getValue());
assertEquals("http://foo/Patient/111/_history?_since="+expectedDateString+"&_count=12", capt.getAllValues().get(1).getURI().toString()); assertEquals("http://foo/Patient/111/_history?_since=" + expectedDateString + "&_count=12", capt.getAllValues().get(1).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12)); client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString()); assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
@ -514,6 +506,22 @@ public class ClientTest {
} }
@Test
public void testNonAnnotatedMethodFailsGracefully() {
// TODO: remove the read annotation and make sure we get a sensible
// error message to tell the user why the method isn't working
ClientWithoutAnnotation client = new FhirContext().newRestfulClient(ClientWithoutAnnotation.class, "http://wildfhir.aegis.net/fhir");
try {
client.read(new IdDt("8"));
fail();
} catch (UnsupportedOperationException e) {
assertThat(e.getMessage(), containsString("annotation"));
}
}
@Test @Test
public void testRead() throws Exception { public void testRead() throws Exception {
@ -539,7 +547,8 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
// Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123")); // Patient response = client.findPatientByMrn(new
// IdentifierDt("urn:foo", "123"));
Patient response = client.getPatientById(new IdDt("111")); Patient response = client.getPatientById(new IdDt("111"));
assertEquals("http://foo/Patient/111", capt.getValue().getURI().toString()); assertEquals("http://foo/Patient/111", capt.getValue().getURI().toString());
@ -550,6 +559,31 @@ public class ClientTest {
} }
@Test
public void testReadFailureNoCharset() throws Exception {
//@formatter:off
String msg = "<OperationOutcome xmlns=\"http://hl7.org/fhir\"></OperationOutcome>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 404, "NOT FOUND"));
Header[] headers = new Header[1];
headers[0] = new BasicHeader(Constants.HEADER_LAST_MODIFIED, "2011-01-02T22:01:02");
when(httpResponse.getAllHeaders()).thenReturn(headers);
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
try {
client.getPatientById(new IdDt("111"));
fail();
} catch (ResourceNotFoundException e) {
// good
}
}
@Test @Test
public void testReadNoCharset() throws Exception { public void testReadNoCharset() throws Exception {
@ -576,7 +610,8 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
// Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123")); // Patient response = client.findPatientByMrn(new
// IdentifierDt("urn:foo", "123"));
Patient response = client.getPatientById(new IdDt("111")); Patient response = client.getPatientById(new IdDt("111"));
assertEquals("http://foo/Patient/111", capt.getValue().getURI().toString()); assertEquals("http://foo/Patient/111", capt.getValue().getURI().toString());
@ -587,49 +622,6 @@ public class ClientTest {
} }
private interface ClientWithoutAnnotation extends IBasicClient {
@Read
Patient read(@IdParam IdDt theId);
}
@Test
public void testIt() {
// TODO: remove the read annotation and make sure we get a sensible
// error message to tell the user why the method isn't working
ClientWithoutAnnotation client = new FhirContext().newRestfulClient(ClientWithoutAnnotation.class, "http://wildfhir.aegis.net/fhir");
client.read(new IdDt("8"));
}
@Test
public void testReadFailureNoCharset() throws Exception {
//@formatter:off
String msg = "<OperationOutcome xmlns=\"http://hl7.org/fhir\"></OperationOutcome>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 404, "NOT FOUND"));
Header[] headers = new Header[1];
headers[0] = new BasicHeader(Constants.HEADER_LAST_MODIFIED, "2011-01-02T22:01:02");
when(httpResponse.getAllHeaders()).thenReturn(headers);
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
Patient response = client.getPatientById(new IdDt("111"));
assertEquals("http://foo/Patient/111", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
InstantDt lm = (InstantDt) response.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED);
assertEquals("2011-01-02T22:01:02", lm.getValueAsString());
}
@Test @Test
public void testSearchByDateRange() throws Exception { public void testSearchByDateRange() throws Exception {
@ -670,35 +662,6 @@ public class ClientTest {
} }
@Test
public void testSearchWithFormatAndPrettyPrint() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
// TODO: document this
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getAllValues().get(0).getURI().toString());
client.setEncoding(EncodingEnum.JSON);
client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02&_format=json", capt.getAllValues().get(1).getURI().toString());
client.setPrettyPrint(true);
client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02&_format=json&_pretty=true", capt.getAllValues().get(2).getURI().toString());
}
@Test @Test
public void testSearchByToken() throws Exception { public void testSearchByToken() throws Exception {
@ -718,7 +681,6 @@ public class ClientTest {
} }
@Test @Test
public void testSearchComposite() throws Exception { public void testSearchComposite() throws Exception {
@ -814,6 +776,35 @@ public class ClientTest {
} }
@Test
public void testSearchWithFormatAndPrettyPrint() throws Exception {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
// TODO: document this
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getAllValues().get(0).getURI().toString());
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
client.setEncoding(EncodingEnum.JSON); // this needs to be actually
// implemented
client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02&_format=json", capt.getAllValues().get(1).getURI().toString());
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
client.setPrettyPrint(true);
client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02"));
assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02&_format=json&_pretty=true", capt.getAllValues().get(2).getURI().toString());
}
@Test @Test
public void testSearchWithIncludes() throws Exception { public void testSearchWithIncludes() throws Exception {
@ -908,7 +899,8 @@ public class ClientTest {
} }
/** /**
* Return a FHIR content type, but no content and make sure we handle this without crashing * Return a FHIR content type, but no content and make sure we handle this
* without crashing
*/ */
@Test @Test
public void testUpdateWithEmptyResponse() throws Exception { public void testUpdateWithEmptyResponse() throws Exception {
@ -1015,7 +1007,8 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
// Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123")); // Patient response = client.findPatientByMrn(new
// IdentifierDt("urn:foo", "123"));
Patient response = client.getPatientByVersionId(new IdDt("111"), new IdDt("999")); Patient response = client.getPatientByVersionId(new IdDt("111"), new IdDt("999"));
assertEquals("http://foo/Patient/111/_history/999", capt.getValue().getURI().toString()); assertEquals("http://foo/Patient/111/_history/999", capt.getValue().getURI().toString());
@ -1055,24 +1048,27 @@ public class ClientTest {
return new Header[] { new BasicHeader(theName, theValue) }; return new Header[] { new BasicHeader(theName, theValue) };
} }
@ResourceDef(name="Patient") @ResourceDef(name = "Patient")
public static class CustomPatient extends Patient public static class CustomPatient extends Patient {
{
// nothing // nothing
} }
public interface ITestClientWithCustomType extends IBasicClient { public interface ITestClientWithCustomType extends IBasicClient {
@Search() @Search()
public CustomPatient getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); public CustomPatient getPatientByDob(@RequiredParam(name = Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
} }
public interface ITestClientWithCustomTypeList extends IBasicClient { public interface ITestClientWithCustomTypeList extends IBasicClient {
@Search() @Search()
public List<CustomPatient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate); public List<CustomPatient> getPatientByDob(@RequiredParam(name = Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
} }
public interface ITestClientWithStringIncludes extends IBasicClient { public interface ITestClientWithStringIncludes extends IBasicClient {
@Search() @Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam String theInclude); public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam String theInclude);
} }
private interface ClientWithoutAnnotation extends IBasicClient {
Patient read(@IdParam IdDt theId);
}
} }

View File

@ -1,26 +0,0 @@
package ca.uhn.fhir.rest.client;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.ClientTest.CustomPatient;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
public class InvalidClientDefinitionTest {
@Test
public void testAnnotationTypeIsNotAssignableToMethodReturnType() {
// TODO: this should fail
new FhirContext().newRestfulClient(ITestClientWithCustomType.class, "http://example.com");
}
public interface ITestClientWithCustomType extends IBasicClient {
@Search(type=Patient.class)
public CustomPatient getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
}
}

View File

@ -0,0 +1,270 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.StringReader;
import java.nio.charset.Charset;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.HeaderElement;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.TagListParam;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.server.Constants;
public class TagsClientTest {
private FhirContext ctx;
private HttpClient httpClient;
private HttpResponse httpResponse;
// atom-document-large.xml
@Before
public void before() {
ctx = new FhirContext(Patient.class, Conformance.class);
httpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ctx.getRestfulClientFactory().setHttpClient(httpClient);
httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
@Test
public void testGetAllTags() throws Exception {
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
String ser = ctx.newXmlParser().encodeTagListToString(tagList);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ser), Charset.forName("UTF-8")));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
TagList response = client.getAllTags();
assertEquals(tagList, response);
assertEquals(HttpGet.class, capt.getValue().getClass());
HttpGet get = (HttpGet) capt.getValue();
assertEquals("http://foo/_tags", get.getURI().toString());
}
@Test
public void testGetAllTagsPatient() throws Exception {
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
String ser = ctx.newXmlParser().encodeTagListToString(tagList);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ser), Charset.forName("UTF-8")));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
TagList response = client.getAllTagsPatient();
assertEquals(tagList, response);
assertEquals(HttpGet.class, capt.getValue().getClass());
HttpGet get = (HttpGet) capt.getValue();
assertEquals("http://foo/Patient/_tags", get.getURI().toString());
}
@Test
public void testDeleteTagsPatient() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType().getElements()).thenReturn(new HeaderElement[0]);
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
client.deleteTags(new IdDt("111"), tagList);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertEquals("http://foo/Patient/111/_tags/_delete", post.getURI().toString());
String ser = IOUtils.toString(post.getEntity().getContent());
TagList actualTagList = ctx.newXmlParser().parseTagList(ser);
assertEquals(tagList, actualTagList);
}
@Test
public void testDeleteTagsPatientVersion() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType().getElements()).thenReturn(new HeaderElement[0]);
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
client.deleteTags(new IdDt("111"), new IdDt("222"),tagList);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertEquals("http://foo/Patient/111/_history/222/_tags/_delete", post.getURI().toString());
String ser = IOUtils.toString(post.getEntity().getContent());
TagList actualTagList = ctx.newXmlParser().parseTagList(ser);
assertEquals(tagList, actualTagList);
}
@Test
public void testAddTagsPatient() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType().getElements()).thenReturn(new HeaderElement[0]);
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
client.addTags(new IdDt("111"), tagList);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertEquals("http://foo/Patient/111/_tags", post.getURI().toString());
String ser = IOUtils.toString(post.getEntity().getContent());
TagList actualTagList = ctx.newXmlParser().parseTagList(ser);
assertEquals(tagList, actualTagList);
}
@Test
public void testAddTagsPatientVersion() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType().getElements()).thenReturn(new HeaderElement[0]);
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
client.addTags(new IdDt("111"), new IdDt("222"),tagList);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertEquals("http://foo/Patient/111/_history/222/_tags", post.getURI().toString());
String ser = IOUtils.toString(post.getEntity().getContent());
TagList actualTagList = ctx.newXmlParser().parseTagList(ser);
assertEquals(tagList, actualTagList);
}
@Test
public void testGetAllTagsPatientId() throws Exception {
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
String ser = ctx.newXmlParser().encodeTagListToString(tagList);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ser), Charset.forName("UTF-8")));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
TagList response = client.getAllTagsPatientId(new IdDt("111"));
assertEquals(tagList, response);
assertEquals(HttpGet.class, capt.getValue().getClass());
HttpGet get = (HttpGet) capt.getValue();
assertEquals("http://foo/Patient/111/_tags", get.getURI().toString());
}
@Test
public void testGetAllTagsPatientIdVersion() throws Exception {
TagList tagList = new TagList();
tagList.add(new Tag("AAA", "BBB", "CCC"));
String ser = ctx.newXmlParser().encodeTagListToString(tagList);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ser), Charset.forName("UTF-8")));
IClient client = ctx.newRestfulClient(IClient.class, "http://foo");
TagList response = client.getAllTagsPatientIdVersion(new IdDt("111"), new IdDt("222"));
assertEquals(tagList, response);
assertEquals(HttpGet.class, capt.getValue().getClass());
HttpGet get = (HttpGet) capt.getValue();
assertEquals("http://foo/Patient/111/_history/222/_tags", get.getURI().toString());
}
private interface IClient extends IBasicClient {
@GetTags
public TagList getAllTags();
@GetTags(type = Patient.class)
public TagList getAllTagsPatient();
@GetTags(type = Patient.class)
public TagList getAllTagsPatientId(@IdParam IdDt theId);
@GetTags(type = Patient.class)
public TagList getAllTagsPatientIdVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersion);
@AddTags(type = Patient.class)
public void addTags(@IdParam IdDt theId, @TagListParam TagList theTagList);
@AddTags(type = Patient.class)
public void addTags(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @TagListParam TagList theTagList);
@DeleteTags(type = Patient.class)
public void deleteTags(@IdParam IdDt theId, @TagListParam TagList theTagList);
@DeleteTags(type = Patient.class)
public void deleteTags(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @TagListParam TagList theTagList);
}
}

View File

@ -24,7 +24,7 @@ public class Tester {
} catch (NonFhirResponseException e) { } catch (NonFhirResponseException e) {
e.printStackTrace(); e.printStackTrace();
System.out.println(e.getResponseText()); System.out.println(e.getResponseBody());
} }
} }

View File

@ -6,7 +6,6 @@ import static org.junit.Assert.*;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.hamcrest.core.StringContains;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
@ -59,6 +58,7 @@ public class DocumentationTest {
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
@SuppressWarnings("unused")
public static class SearchProvider { public static class SearchProvider {
@Search(type = Patient.class) @Search(type = Patient.class)

View File

@ -921,11 +921,8 @@ public class ResfulServerMethodTest {
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/DiagnosticReport/001"); HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/DiagnosticReport/001");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost); ourClient.execute(httpPost);
fail();
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/DiagnosticReport/001/_history/002", status.getFirstHeader("Location").getValue());
} }
@Test @Test
@ -1199,12 +1196,11 @@ public class ResfulServerMethodTest {
return DiagnosticReport.class; return DiagnosticReport.class;
} }
@SuppressWarnings("unused")
@Update() @Update()
public MethodOutcome updateDiagnosticReportWithNoResponse(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport thePatient, TagList theTagList) { public MethodOutcome updateDiagnosticReportWithNoResponse(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId, @ResourceParam DiagnosticReport theDr) {
IdDt id = theId; IdDt id = theId;
IdDt version = theVersionId; IdDt version = theVersionId;
myLastTags = theTagList; myLastTags = (TagList) theDr.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
return new MethodOutcome(id, version); return new MethodOutcome(id, version);
} }
@ -1212,12 +1208,11 @@ public class ResfulServerMethodTest {
return myLastTags; return myLastTags;
} }
@SuppressWarnings("unused")
@Update() @Update()
public MethodOutcome updateDiagnosticReportWithVersionAndNoResponse(@IdParam IdDt theId, @ResourceParam DiagnosticReport thePatient, TagList theTagList) { public MethodOutcome updateDiagnosticReportWithVersionAndNoResponse(@IdParam IdDt theId, @ResourceParam DiagnosticReport theDr) {
IdDt id = theId; IdDt id = theId;
IdDt version = new IdDt("002"); IdDt version = new IdDt("002");
myLastTags=theTagList; myLastTags = (TagList) theDr.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
return new MethodOutcome(id, version); return new MethodOutcome(id, version);
} }

View File

@ -46,7 +46,7 @@ public class ResourceMethodTest {
inputParams.add("firstName"); inputParams.add("firstName");
inputParams.add("lastName"); inputParams.add("lastName");
assertEquals(false, rm.matches(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // False assertEquals(false, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // False
} }
@Test @Test
@ -61,7 +61,7 @@ public class ResourceMethodTest {
Set<String> inputParams = new HashSet<String>(); Set<String> inputParams = new HashSet<String>();
inputParams.add("mrn"); inputParams.add("mrn");
assertEquals(true, rm.matches(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True assertEquals(true, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True
} }
@Test @Test
@ -78,7 +78,7 @@ public class ResourceMethodTest {
inputParams.add("firstName"); inputParams.add("firstName");
inputParams.add("mrn"); inputParams.add("mrn");
assertEquals(true, rm.matches(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True assertEquals(true, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True
} }
@Test @Test
@ -96,7 +96,7 @@ public class ResourceMethodTest {
inputParams.add("lastName"); inputParams.add("lastName");
inputParams.add("mrn"); inputParams.add("mrn");
assertEquals(true, rm.matches(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True assertEquals(true, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // True
} }
@Test @Test
@ -115,6 +115,6 @@ public class ResourceMethodTest {
inputParams.add("mrn"); inputParams.add("mrn");
inputParams.add("foo"); inputParams.add("foo");
assertEquals(false, rm.matches(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // False assertEquals(false, rm.incomingServerRequestMatchesMethod(Request.withResourceAndParams("Patient", RequestType.GET, inputParams))); // False
} }
} }

View File

@ -0,0 +1,294 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.api.annotation.TagListParam;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class TagsServerTest {
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
private static String ourLastOutcome;
private static TagList ourLastTagList;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TagsServerTest.class);
private static int ourPort;
private static DummyProvider ourProvider;
private static Server ourServer;
@Before
public void before() {
ourLastOutcome = null;
ourLastTagList = null;
}
@Test
public void testAddTagsById() throws Exception {
TagList tagList = new TagList();
tagList.addTag("term", "label", "scheme");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/111/_tags");
httpPost.setEntity(new StringEntity(ourCtx.newJsonParser().encodeTagListToString(tagList), ContentType.create(EncodingEnum.JSON.getResourceContentType(), "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("add111", ourLastOutcome);
assertEquals(tagList, ourLastTagList);
}
@Test
public void testAddTagsByIdAndVersion() throws Exception {
TagList tagList = new TagList();
tagList.addTag("term", "label", "scheme");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/111/_history/222/_tags");
httpPost.setEntity(new StringEntity(ourCtx.newJsonParser().encodeTagListToString(tagList), ContentType.create(EncodingEnum.JSON.getResourceContentType(), "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("add111222", ourLastOutcome);
assertEquals(tagList, ourLastTagList);
}
@Test
public void testEquals() {
TagList list1 = ourProvider.getAllTagsPatient();
TagList list2 = ourProvider.getAllTagsPatient();
assertEquals(list1, list2);
list1 = ourProvider.getAllTagsPatient();
list2 = ourProvider.getAllTagsPatient();
list2.get(0).setTerm("!!!!!");
assertNotEquals(list1, list2);
}
@Test
public void testGetAllTags() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_tags");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
TagList tagList = ourCtx.newXmlParser().parseTagList(responseContent);
assertEquals(ourProvider.getAllTags(), tagList);
}
@Test
public void testGetAllTagsPatient() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_tags");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
TagList actual = ourCtx.newXmlParser().parseTagList(responseContent);
TagList expected = ourProvider.getAllTags();
expected.get(0).setTerm("Patient");
assertEquals(expected, actual);
}
@Test
public void testGetAllTagsPatientId() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/111/_tags");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
TagList actual = ourCtx.newXmlParser().parseTagList(responseContent);
TagList expected = ourProvider.getAllTags();
expected.get(0).setTerm("Patient111");
assertEquals(expected, actual);
}
@Test
public void testGetAllTagsPatientIdVersion() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/111/_history/222/_tags");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
TagList actual = ourCtx.newXmlParser().parseTagList(responseContent);
TagList expected = ourProvider.getAllTags();
expected.get(0).setTerm("Patient111222");
assertEquals(expected, actual);
}
@Test
public void testRemoveTagsById() throws Exception {
TagList tagList = new TagList();
tagList.addTag("term", "label", "scheme");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/111/_tags/_delete");
httpPost.setEntity(new StringEntity(ourCtx.newJsonParser().encodeTagListToString(tagList), ContentType.create(EncodingEnum.JSON.getResourceContentType(), "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("Remove111", ourLastOutcome);
assertEquals(tagList, ourLastTagList);
}
@Test
public void testRemoveTagsByIdAndVersion() throws Exception {
TagList tagList = new TagList();
tagList.addTag("term", "label", "scheme");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/111/_history/222/_tags/_delete");
httpPost.setEntity(new StringEntity(ourCtx.newJsonParser().encodeTagListToString(tagList), ContentType.create(EncodingEnum.JSON.getResourceContentType(), "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("Remove111222", ourLastOutcome);
assertEquals(tagList, ourLastTagList);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
ourCtx = new FhirContext(Patient.class);
ourProvider = new DummyProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setPlainProviders(ourProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyProvider {
@AddTags(type = Patient.class)
public void addTagsPatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @TagListParam TagList theTagList) {
ourLastOutcome = "add" + theId.getValue() + theVersion.getValue();
ourLastTagList=theTagList;
}
@AddTags(type = Patient.class)
public void addTagsPatient(@IdParam IdDt theId, @TagListParam TagList theTagList) {
ourLastOutcome = "add" + theId.getValue();
ourLastTagList=theTagList;
}
@GetTags
public TagList getAllTags() {
TagList tagList = new TagList();
tagList.add(new Tag("AllDog", "DogLabel", (String) null));
tagList.add(new Tag("AllCat", "CatLabel", "http://cats"));
return tagList;
}
@GetTags(type = Patient.class)
public TagList getAllTagsPatient() {
TagList tagList = new TagList();
tagList.add(new Tag("Patient", "DogLabel", (String) null));
tagList.add(new Tag("AllCat", "CatLabel", "http://cats"));
return tagList;
}
@GetTags(type = Patient.class)
public TagList getAllTagsPatientId(@IdParam IdDt theId) {
TagList tagList = new TagList();
tagList.add(new Tag("Patient" + theId.getValue(), "DogLabel", (String) null));
tagList.add(new Tag("AllCat", "CatLabel", "http://cats"));
return tagList;
}
@GetTags(type = Patient.class)
public TagList getAllTagsPatientIdVersion(@IdParam IdDt theId, @VersionIdParam IdDt theVersion) {
TagList tagList = new TagList();
tagList.add(new Tag("Patient" + theId.getValue() + theVersion.getValue(), "DogLabel", (String) null));
tagList.add(new Tag("AllCat", "CatLabel", "http://cats"));
return tagList;
}
@DeleteTags(type = Patient.class)
public void RemoveTagsPatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @TagListParam TagList theTagList) {
ourLastOutcome = "Remove" + theId.getValue() + theVersion.getValue();
ourLastTagList=theTagList;
}
@DeleteTags(type = Patient.class)
public void RemoveTagsPatient(@IdParam IdDt theId, @TagListParam TagList theTagList) {
ourLastOutcome = "Remove" + theId.getValue();
ourLastTagList=theTagList;
}
}
}

View File

@ -239,7 +239,9 @@ public class FhirResourceDao<T extends IResource, X extends BaseResourceTable<T>
String path = param.getPath(); String path = param.getPath();
List<Object> values = terser.getValues(resource, path); List<Object> values = terser.getValues(resource, path);
for (Object nextValue : values) { for (Object nextValue : values) {
if (nextValue.equals(nextParamEntry.getValue())) { IQueryParameterType expectedQt = nextParamEntry.getValue();
IQueryParameterType actualQt = (IQueryParameterType)nextValue;
if (actualQt.getValueAsQueryToken().equals(expectedQt.getValueAsQueryToken())) {
shouldAdd = true; shouldAdd = true;
break; break;
} }

View File

@ -6,7 +6,7 @@
<persistence-unit name="FHIR_UT" transaction-type="RESOURCE_LOCAL"> <persistence-unit name="FHIR_UT" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider> <provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>ca.uhn.fhir.jpa.entity.PatientResourceTable</class> <!-- <class>ca.uhn.fhir.jpa.entity.PatientResourceTable</class> -->
<exclude-unlisted-classes>false</exclude-unlisted-classes> <exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties> <properties>
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:unit-testing-jpa" /> <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:unit-testing-jpa" />

View File

@ -60,13 +60,13 @@ public class FhirResourceDaoTest {
@Test @Test
public void testCreateWithTags() { public void testTagsWithCreateAndReadAndSearch() {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier("urn:system", "001"); patient.addIdentifier("urn:system", "testTagsWithCreateAndReadAndSearch");
patient.addName().addFamily("Tester").addGiven("Joe"); patient.addName().addFamily("Tester").addGiven("Joe");
TagList tagList= new TagList(); TagList tagList= new TagList();
tagList.addTag("Dog", "Puppies", null); tagList.addTag("Dog", "Puppies", null);
tagList.addTag("Cat", "Kittens", null); tagList.addTag("Cat", "Kittens", "http://foo");
patient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList); patient.getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList);
MethodOutcome outcome = ourPatientDao.create(patient); MethodOutcome outcome = ourPatientDao.create(patient);
@ -77,10 +77,27 @@ public class FhirResourceDaoTest {
TagList published = (TagList) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); TagList published = (TagList) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
assertEquals(2, published.size()); assertEquals(2, published.size());
assertEquals("Dog", published.get(0).getTerm()); assertEquals("Dog", published.get(0).getTerm());
assertEquals("Puppies", published.get(0).getLabel());
assertEquals(null, published.get(0).getScheme());
assertEquals("Cat", published.get(1).getTerm()); assertEquals("Cat", published.get(1).getTerm());
assertEquals("Kittens", published.get(1).getLabel());
assertEquals("http://foo", published.get(1).getScheme());
List<Patient> search = ourPatientDao.search(Patient.SP_IDENTIFIER, patient.getIdentifierFirstRep());
assertEquals(1,search.size());
retrieved = search.get(0);
published = (TagList) retrieved.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
assertEquals("Dog", published.get(0).getTerm());
assertEquals("Puppies", published.get(0).getLabel());
assertEquals(null, published.get(0).getScheme());
assertEquals("Cat", published.get(1).getTerm());
assertEquals("Kittens", published.get(1).getLabel());
assertEquals("http://foo", published.get(1).getScheme());
} }
@Test @Test
public void testSearchAll() { public void testSearchAll() {
{ {