Documentation fixes

This commit is contained in:
jamesagnew 2014-05-01 17:53:33 -04:00
parent 862b1bd79d
commit a1aedf2f31
11 changed files with 296 additions and 22 deletions

View File

@ -97,7 +97,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
if (name == null) {
if (myIgnoreMissingTemplates) {
ourLog.debug("No narrative template available for profile: {}", theProfile);
return new NarrativeDt(new XhtmlDt("<div>No narrative available</div>"), NarrativeStatusEnum.EMPTY);
return new NarrativeDt(new XhtmlDt("<div>No narrative template available for resource profile: " + theProfile + "</div>"), NarrativeStatusEnum.EMPTY);
} else {
throw new DataFormatException("No narrative template for class " + theResource.getClass().getCanonicalName());
}
@ -120,7 +120,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
} catch (Exception e) {
if (myIgnoreFailures) {
ourLog.error("Failed to generate narrative", e);
return new NarrativeDt(new XhtmlDt("<div>No narrative available</div>"), NarrativeStatusEnum.EMPTY);
return new NarrativeDt(new XhtmlDt("<div>No narrative available - Error: " + e.getMessage() + "</div>"), NarrativeStatusEnum.EMPTY);
} else {
throw new DataFormatException(e);
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.param;
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@ -31,6 +32,59 @@ public class CodingListParam implements IQueryParameterOr, Iterable<CodingDt> {
private List<CodingDt> myCodings = new ArrayList<CodingDt>();
/**
* Constructor. Codings should be added, e.g. using {@link #add(CodingDt)}
*/
public CodingListParam() {
// nothing
}
/**
* Constructor which accepts an array of codings
*
* @param theCodings
* The codings
*/
public CodingListParam(CodingDt... theCodings) {
if (theCodings != null) {
for (CodingDt next : theCodings) {
add(next);
}
}
}
/**
* Constructor which accepts a code system and an array of codes in that system
*
* @param theSystem
* The code system
* @param theCodings
* The codes
*/
public CodingListParam(String theSystem, String... theCodes) {
if (theCodes != null) {
for (String next : theCodes) {
add(new CodingDt(theSystem, next));
}
}
}
/**
* Constructor which accepts a code system and a collection of codes in that system
*
* @param theSystem
* The code system
* @param theCodings
* The codes
*/
public CodingListParam(String theSystem, Collection<String> theCodes) {
if (theCodes != null) {
for (String next : theCodes) {
add(new CodingDt(theSystem, next));
}
}
}
/**
* Returns all Codings associated with this list
*/

View File

@ -26,6 +26,8 @@ import java.util.Date;
import java.util.List;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class DateRangeParam implements IQueryParameterAnd {
@ -33,6 +35,83 @@ public class DateRangeParam implements IQueryParameterAnd {
private QualifiedDateParam myLowerBound;
private QualifiedDateParam myUpperBound;
/**
* Basic constructor. Values must be supplied by calling {@link #setLowerBound(QualifiedDateParam)} and {@link #setUpperBound(QualifiedDateParam)}
*/
public DateRangeParam() {
// nothing
}
/**
* Constructor which takes two strings representing the lower and upper bounds of the range
*
* @param theLowerBound
* A qualified date param representing the lower date bound (optionally may include time), e.g. "2011-02-22" or "2011-02-22T13:12:00"
* @param theLowerBound
* A qualified date param representing the upper date bound (optionally may include time), e.g. "2011-02-22" or "2011-02-22T13:12:00"
*/
public DateRangeParam(String theLowerBound, String theUpperBound) {
myLowerBound = new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theLowerBound);
myUpperBound = new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theUpperBound);
validateAndThrowDataFormatExceptionIfInvalid();
}
/**
* Constructor which takes two Dates representing the lower and upper bounds of the range
*
* @param theLowerBound
* A qualified date param representing the lower date bound (optionally may include time), e.g. "2011-02-22" or "2011-02-22T13:12:00"
* @param theLowerBound
* A qualified date param representing the upper date bound (optionally may include time), e.g. "2011-02-22" or "2011-02-22T13:12:00"
*/
public DateRangeParam(Date theLowerBound, Date theUpperBound) {
myLowerBound = new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, theLowerBound);
myUpperBound = new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, theUpperBound);
validateAndThrowDataFormatExceptionIfInvalid();
}
private void validateAndThrowDataFormatExceptionIfInvalid() {
boolean haveLowerBound = myLowerBound != null && myLowerBound.isEmpty() == false;
boolean haveUpperBound = myUpperBound != null && myUpperBound.isEmpty() == false;
if (haveLowerBound && haveUpperBound) {
if (myLowerBound.getValue().after(myUpperBound.getValue())) {
throw new DataFormatException("Lower bound of " + myLowerBound.getValueAsString() + " is after upper bound of " + myUpperBound.getValueAsString());
}
}
if (haveLowerBound) {
if (myLowerBound.getComparator() == null) {
myLowerBound.setComparator(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS);
}
switch (myLowerBound.getComparator()) {
case GREATERTHAN:
case GREATERTHAN_OR_EQUALS:
default:
break;
case LESSTHAN:
case LESSTHAN_OR_EQUALS:
throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + myLowerBound.getComparator().getCode());
}
}
if (haveUpperBound) {
if (myUpperBound.getComparator() == null) {
myUpperBound.setComparator(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS);
}
switch (myUpperBound.getComparator()) {
case LESSTHAN:
case LESSTHAN_OR_EQUALS:
default:
break;
case GREATERTHAN:
case GREATERTHAN_OR_EQUALS:
throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + myUpperBound.getComparator().getCode());
}
}
}
private void addParam(QualifiedDateParam theParsed) throws InvalidRequestException {
if (theParsed.getComparator() == null) {
if (myLowerBound != null || myUpperBound != null) {
@ -88,10 +167,12 @@ public class DateRangeParam implements IQueryParameterAnd {
public void setLowerBound(QualifiedDateParam theLowerBound) {
myLowerBound = theLowerBound;
validateAndThrowDataFormatExceptionIfInvalid();
}
public void setUpperBound(QualifiedDateParam theUpperBound) {
myUpperBound = theUpperBound;
validateAndThrowDataFormatExceptionIfInvalid();
}
@Override

View File

@ -20,7 +20,6 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -28,6 +27,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.rest.annotation.IncludeParam;
@ -38,8 +38,9 @@ public class IncludeParameter extends BaseQueryParameter {
private Class<? extends Collection<PathSpecification>> myInstantiableCollectionType;
private HashSet<String> myAllow;
private Class<?> mySpecType;
public IncludeParameter(IncludeParam theAnnotation, Class<? extends Collection<PathSpecification>> theInstantiableCollectionType) {
public IncludeParameter(IncludeParam theAnnotation, Class<? extends Collection<PathSpecification>> theInstantiableCollectionType, Class<?> theSpecType) {
myInstantiableCollectionType = theInstantiableCollectionType;
if (theAnnotation.allow().length > 0) {
myAllow = new HashSet<String>();
@ -47,6 +48,12 @@ public class IncludeParameter extends BaseQueryParameter {
myAllow.add(next);
}
}
mySpecType = theSpecType;
if (mySpecType != PathSpecification.class && mySpecType != String.class) {
throw new ConfigurationException("Invalid @" + IncludeParam.class.getSimpleName() + " parameter type: " + mySpecType);
}
}
@SuppressWarnings("unchecked")
@ -54,10 +61,18 @@ public class IncludeParameter extends BaseQueryParameter {
public List<List<String>> encode(Object theObject) throws InternalErrorException {
ArrayList<List<String>> retVal = new ArrayList<List<String>>();
if (myInstantiableCollectionType == null) {
if (mySpecType == PathSpecification.class) {
retVal.add(Collections.singletonList(((PathSpecification)theObject).getValue()));
} else {
retVal.add(Collections.singletonList(((String)theObject)));
}
}else {
Collection<PathSpecification> val = (Collection<PathSpecification>) theObject;
for (PathSpecification pathSpec : val) {
retVal.add(Collections.singletonList(pathSpec.getValue()));
}
}
return retVal;
}
@ -69,12 +84,14 @@ public class IncludeParameter extends BaseQueryParameter {
@Override
public Object parse(List<List<String>> theString) throws InternalErrorException, InvalidRequestException {
Collection<PathSpecification> retVal;
Collection<PathSpecification> retValCollection = null;
if (myInstantiableCollectionType!=null) {
try {
retVal = myInstantiableCollectionType.newInstance();
retValCollection = myInstantiableCollectionType.newInstance();
} catch (Exception e) {
throw new InternalErrorException("Failed to instantiate " + myInstantiableCollectionType.getName(), e);
}
}
for (List<String> nextParamList : theString) {
if (nextParamList.isEmpty()) {
@ -90,10 +107,18 @@ public class IncludeParameter extends BaseQueryParameter {
throw new InvalidRequestException("Invalid _include parameter value: '" + value + "'. Valid values are: " + new TreeSet<String>(myAllow));
}
}
retVal.add(new PathSpecification(value));
if (retValCollection == null) {
if (mySpecType == String.class) {
return value;
} else {
return new PathSpecification(value);
}
}else {
retValCollection.add(new PathSpecification(value));
}
}
return retVal;
return retValCollection;
}
@Override

View File

@ -106,12 +106,20 @@ public class ParameterUtil {
extractDescription(parameter, annotations);
param = parameter;
} else if (nextAnnotation instanceof IncludeParam) {
if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + PathSpecification.class.getSimpleName() + ">");
}
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
Class<? extends Collection<PathSpecification>> instantiableCollectionType;
Class<?> specType;
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType);
if (parameterType == String.class) {
instantiableCollectionType=null;
specType=String.class;
}else if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + PathSpecification.class.getSimpleName() + ">");
} else {
instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
specType = PathSpecification.class;
}
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType, specType);
} else if (nextAnnotation instanceof ResourceParam) {
if (!IResource.class.isAssignableFrom(parameterType)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());

View File

@ -25,6 +25,7 @@ import java.util.Date;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.parser.DataFormatException;
public class QualifiedDateParam extends DateTimeDt implements IQueryParameterType {
@ -76,7 +77,7 @@ public class QualifiedDateParam extends DateTimeDt implements IQueryParameterTyp
@Override
public void setValueAsQueryToken(String theParameter) {
if (theParameter.length() < 2) {
throw new IllegalArgumentException("Invalid qualified date parameter: "+theParameter);
throw new DataFormatException("Invalid qualified date parameter: "+theParameter);
}
char char0 = theParameter.charAt(0);
@ -92,7 +93,7 @@ public class QualifiedDateParam extends DateTimeDt implements IQueryParameterTyp
String comparatorString = theParameter.substring(0, dateStart);
QuantityCompararatorEnum comparator = QuantityCompararatorEnum.VALUESET_BINDER.fromCodeString(comparatorString);
if (comparator==null) {
throw new IllegalArgumentException("Invalid date qualifier: "+comparatorString);
throw new DataFormatException("Invalid date qualifier: "+comparatorString);
}
String dateString = theParameter.substring(dateStart);

View File

@ -49,16 +49,15 @@ import org.thymeleaf.templateresolver.TemplateResolver;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.client.GenericClient;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.testmodel.IdentifierDt;
public class PublicTesterServlet extends HttpServlet {

View File

@ -250,6 +250,36 @@ public List<DiagnosticReport> getDiagnosticReport(
}
//END SNIPPET: pathSpec
//START SNIPPET: pathSpecSimple
@Search()
public List<DiagnosticReport> getDiagnosticReport(
@RequiredParam(name=DiagnosticReport.SP_IDENTIFIER)
IdentifierDt theIdentifier,
@IncludeParam(allow= {"DiagnosticReport.subject"})
String theInclude ) {
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
// Assume this method exists and loads the report from the DB
DiagnosticReport report = loadSomeDiagnosticReportFromDatabase(theIdentifier);
// If the client has asked for the subject to be included:
if ("DiagnosticReport.subject".equals(theInclude)) {
// The resource reference should contain the ID of the patient
IdDt subjectId = report.getSubject().getId();
// So load the patient ID and return it
Patient subject = loadSomePatientFromDatabase(subjectId);
report.getSubject().setResource(subject);
}
retVal.add(report);
return retVal;
}
//END SNIPPET: pathSpecSimple
//START SNIPPET: dateRange
@Search()
public List<Observation> getObservationsByDateRange(@RequiredParam(name="subject.identifier") IdentifierDt theSubjectId,

View File

@ -729,6 +729,17 @@
<code>http://fhir.example.com/DiagnosticReport?subject.identifier=7000135&amp;_include=DiagnosticReport.subject</code>
</p>
<p>
It is also possible to use a String type for the include parameter,
which is more convenient if only a single include (or null for none)
is all that is required.
</p>
<macro name="snippet">
<param name="id" value="pathSpecSimple" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</subsection>
<subsection name="Named Queries (_query)">

View File

@ -48,6 +48,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
@ -637,6 +638,29 @@ public class ClientTest {
}
@Test
public void testSearchWithStringIncludes() 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")));
ITestClientWithStringIncludes client = ctx.newRestfulClient(ITestClientWithStringIncludes.class, "http://foo");
client.getPatientWithIncludes(new StringDt("aaa"), "inc1");
assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1", capt.getValue().getURI().toString());
}
public interface ITestClientWithStringIncludes extends IBasicClient {
@Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam String theInclude);
}
@Test
public void testSearchWithOptionalParam() throws Exception {

View File

@ -46,6 +46,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.AdverseReaction;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.DiagnosticOrder;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
@ -531,6 +532,20 @@ public class ResfulServerMethodTest {
}
@Test
public void testReadOnTypeThatDoesntSupportRead() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/AdverseReaction/223");
HttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(Constants.STATUS_HTTP_404_NOT_FOUND, status.getStatusLine().getStatusCode());
}
@Test
public void testSearchAllProfiles() throws Exception {
@ -999,9 +1014,10 @@ public class ResfulServerMethodTest {
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServerProfileProvider profProvider = new ServerProfileProvider(ourCtx);
DummyDiagnosticReportResourceProvider reportProvider = new DummyDiagnosticReportResourceProvider();
DummyAdverseReactionResourceProvider adv = new DummyAdverseReactionResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
DummyRestfulServer servlet = new DummyRestfulServer(patientProvider, profProvider, reportProvider);
DummyRestfulServer servlet = new DummyRestfulServer(patientProvider, profProvider, reportProvider, adv);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
@ -1061,6 +1077,31 @@ public class ResfulServerMethodTest {
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyAdverseReactionResourceProvider implements IResourceProvider {
/*
* *********************
* NO NEW METHODS
* *********************
*/
@Override
public Class<? extends IResource> getResourceType() {
return AdverseReaction.class;
}
@Create()
public MethodOutcome create(@ResourceParam AdverseReaction thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
IdDt version = new IdDt(thePatient.getIdentifier().get(1).getValue().getValue());
return new MethodOutcome(id, version);
}
}
/**
* Created by dsotnikov on 2/25/2014.
*/