From df4d371edc7e3293f9c90530fbfa965d7322272b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 24 Aug 2015 15:23:28 -0400 Subject: [PATCH] Add profile and security params for generic search --- .../java/example/GenericClientExample.java | 21 +- .../main/java/ca/uhn/fhir/parser/IParser.java | 6 + .../java/ca/uhn/fhir/parser/JsonParser.java | 47 ++- .../java/ca/uhn/fhir/parser/XmlParser.java | 140 ++++--- .../ca/uhn/fhir/rest/annotation/Elements.java | 20 + .../ca/uhn/fhir/rest/api/SummaryEnum.java | 20 + .../ca/uhn/fhir/rest/client/BaseClient.java | 9 +- .../uhn/fhir/rest/client/GenericClient.java | 76 +++- .../fhir/rest/gclient/IClientExecutable.java | 9 +- .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 16 + .../BaseResourceReturningMethodBinding.java | 24 +- .../server/IVersionSpecificBundleFactory.java | 4 +- .../uhn/fhir/rest/server/RestfulServer.java | 46 +- .../fhir/rest/server/RestfulServerUtils.java | 95 +++-- .../ExceptionHandlingInterceptor.java | 8 +- .../ResponseHighlighterInterceptor.java | 35 +- .../jpa/dao/FhirResourceDaoValueSetDstu2.java | 20 + .../jpa/dao/IFhirResourceDaoValueSet.java | 20 + .../BaseJpaResourceProviderValueSetDstu2.java | 20 + .../fhir/jpa/dao/FhirSystemDaoDstu2Test.java | 18 + .../src/test/resources/josh-bundle.json | 39 ++ .../fhir/rest/client/GenericClientTest.java | 394 ++++++++++-------- .../uhn/fhir/rest/server/ExceptionTest.java | 7 +- .../rest/client/GenericClientDstu2Test.java | 82 ++++ .../ResponseHighlightingInterceptorTest.java | 13 + pom.xml | 80 +++- src/site/xdoc/doc_rest_client.xml | 27 ++ src/site/xdoc/doc_rest_server.xml | 4 +- 28 files changed, 896 insertions(+), 404 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/test/resources/josh-bundle.json diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java index 388830a4c16..a58856eb0ea 100644 --- a/examples/src/main/java/example/GenericClientExample.java +++ b/examples/src/main/java/example/GenericClientExample.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.method.SearchStyleEnum; @@ -259,10 +260,28 @@ public class GenericClientExample { .forResource(Patient.class) .withIdAndCompartment("123", "condition") .where(Patient.ADDRESS.matches().values("Toronto")) - .returnBundle(Bundle.class) + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .execute(); // END SNIPPET: searchCompartment + // START SNIPPET: searchSubsetSummary + response = client.search() + .forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")) + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .summaryMode(SummaryEnum.TRUE) + .execute(); + // END SNIPPET: searchSubsetSummary + + // START SNIPPET: searchSubsetElements + response = client.search() + .forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")) + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .elementsSubset("identifier", "name") // only include the identifier and name + .execute(); + // END SNIPPET: searchSubsetElements + // START SNIPPET: searchAdv response = client.search() .forResource(Patient.class) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java index da413f12edf..cd13f116085 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java @@ -32,6 +32,7 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.rest.server.EncodingEnum; /** * A parser, which can be used to convert between HAPI FHIR model/structure objects, and their respective String wire @@ -294,4 +295,9 @@ public interface IParser { */ IParser setSuppressNarratives(boolean theSuppressNarratives); + /** + * Which encoding does this parser instance produce? + */ + EncodingEnum getEncoding(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index a7ec62c34ce..aa622a449bd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -60,14 +60,12 @@ import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseHasExtensions; import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; import org.hl7.fhir.instance.model.api.IBaseIntegerDatatype; -import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum; @@ -100,7 +98,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.narrative.INarrativeGenerator; -import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.util.ElementUtil; /** @@ -923,6 +921,11 @@ public class JsonParser extends BaseParser implements IParser { } } + @Override + public EncodingEnum getEncoding() { + return EncodingEnum.JSON; + } + private void parseAlternates(JsonValue theAlternateVal, ParserState theState, String theElementName) { if (theAlternateVal == null || theAlternateVal.getValueType() == ValueType.NULL) { return; @@ -1207,25 +1210,6 @@ public class JsonParser extends BaseParser implements IParser { } } - @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 state = ParserState.getPreTagListInstance(myContext, true, getErrorHandler()); - state.enteringNewElement(null, resourceType); - - parseChildren(object, state); - - state.endingElement(); - - return state.getObject(); - } - // private void parseExtensionInDstu2Style(boolean theModifier, ParserState theState, String // theParentExtensionUrl, String theExtensionUrl, JsonArray theValues) { // String extUrl = UrlUtil.constructAbsoluteUrl(theParentExtensionUrl, theExtensionUrl); @@ -1253,6 +1237,25 @@ public class JsonParser extends BaseParser implements IParser { // } + @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 state = ParserState.getPreTagListInstance(myContext, true, getErrorHandler()); + state.enteringNewElement(null, resourceType); + + parseChildren(object, state); + + state.endingElement(); + + return state.getObject(); + } + @Override public IParser setPrettyPrint(boolean thePrettyPrint) { myPrettyPrint = thePrettyPrint; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index cf3bfcef7f0..e7f4581f0ca 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -19,8 +19,9 @@ package ca.uhn.fhir.parser; * limitations under the License. * #L% */ - -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.io.Reader; @@ -83,8 +84,8 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.narrative.INarrativeGenerator; -import ca.uhn.fhir.parser.BaseParser.CompositeChildElement; import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ElementUtil; import ca.uhn.fhir.util.NonPrettyPrintWriterWrapper; @@ -92,8 +93,7 @@ import ca.uhn.fhir.util.PrettyPrintWriterWrapper; import ca.uhn.fhir.util.XmlUtil; /** - * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use - * {@link FhirContext#newXmlParser()} to get an instance. + * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use {@link FhirContext#newXmlParser()} to get an instance. */ public class XmlParser extends BaseParser implements IParser { @@ -112,8 +112,7 @@ public class XmlParser extends BaseParser implements IParser { private boolean myPrettyPrint; /** - * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke - * {@link FhirContext#newXmlParser()}. + * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke {@link FhirContext#newXmlParser()}. * * @param theParserErrorHandler */ @@ -159,6 +158,39 @@ public class XmlParser extends BaseParser implements IParser { } } + @Override + public void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException { + try { + XMLStreamWriter eventWriter = createXmlWriter(theWriter); + if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + encodeBundleToWriterDstu2(theBundle, eventWriter); + } else { + encodeBundleToWriterDstu1(theBundle, eventWriter); + } + } catch (XMLStreamException e) { + throw new ConfigurationException("Failed to initialize STaX event factory", e); + } + } + + @Override + public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException { + XMLStreamWriter eventWriter; + try { + eventWriter = createXmlWriter(theWriter); + + encodeResourceToXmlStreamWriter(theResource, eventWriter, false); + eventWriter.flush(); + } catch (XMLStreamException e) { + throw new ConfigurationException("Failed to initialize STaX event factory", e); + } + } + + @Override + public T doParseResource(Class theResourceType, Reader theReader) { + XMLEventReader streamReader = createStreamReader(theReader); + return parseResource(theResourceType, streamReader); + } + private T doXmlLoop(XMLEventReader streamReader, ParserState parserState) { ourLog.trace("Entering XML parsing loop with state: {}", parserState); @@ -234,20 +266,6 @@ public class XmlParser extends BaseParser implements IParser { return stringWriter.toString(); } - @Override - public void doEncodeBundleToWriter(Bundle theBundle, Writer theWriter) throws DataFormatException { - try { - XMLStreamWriter eventWriter = createXmlWriter(theWriter); - if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { - encodeBundleToWriterDstu2(theBundle, eventWriter); - } else { - encodeBundleToWriterDstu1(theBundle, eventWriter); - } - } catch (XMLStreamException e) { - throw new ConfigurationException("Failed to initialize STaX event factory", e); - } - } - private void encodeBundleToWriterDstu1(Bundle theBundle, XMLStreamWriter eventWriter) throws XMLStreamException { eventWriter.writeStartElement("feed"); eventWriter.writeDefaultNamespace(ATOM_NS); @@ -448,7 +466,8 @@ public class XmlParser extends BaseParser implements IParser { theEventWriter.close(); } - private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase nextValue, String childName, BaseRuntimeElementDefinition childDef, String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { + private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase nextValue, String childName, BaseRuntimeElementDefinition childDef, + String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { if (nextValue == null || nextValue.isEmpty()) { if (isChildContained(childDef, theIncludedResource)) { // We still want to go in.. @@ -503,10 +522,9 @@ public class XmlParser extends BaseParser implements IParser { case CONTAINED_RESOURCE_LIST: case CONTAINED_RESOURCES: { /* - * Disable per #103 for (IResource next : value.getContainedResources()) { if - * (getContainedResources().getResourceId(next) != null) { continue; } - * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, - * fixContainedResourceId(next.getId().getValue())); theEventWriter.writeEndElement(); } + * Disable per #103 for (IResource next : value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; } + * theEventWriter.writeStartElement("contained"); encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(next.getId().getValue())); + * theEventWriter.writeEndElement(); } */ for (IBaseResource next : getContainedResources().getContainedResources()) { IIdType resourceId = getContainedResources().getResourceId(next); @@ -550,7 +568,8 @@ public class XmlParser extends BaseParser implements IParser { } - private void encodeCompositeElementChildrenToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, List theChildren, boolean theContainedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { + private void encodeCompositeElementChildrenToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, List theChildren, + boolean theContainedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { for (CompositeChildElement nextChildElem : super.compositeChildIterator(theChildren, theContainedResource, theParent)) { BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); @@ -576,12 +595,13 @@ public class XmlParser extends BaseParser implements IParser { } if (nextChild instanceof RuntimeChildContainedResources) { - encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, + nextChildElem); } else { List values = nextChild.getAccessor().getValues(theElement); values = super.preProcessValues(nextChild, values); - + if (values == null || values.isEmpty()) { continue; } @@ -623,7 +643,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition theElementDefinition, boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { + private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition theElementDefinition, + boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, theElementDefinition.getExtensions(), theIncludedResource, theParent); encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, theElementDefinition.getChildren(), theIncludedResource, theParent); @@ -645,17 +666,6 @@ public class XmlParser extends BaseParser implements IParser { } } - /** - * This is just to work around the fact that casting java.util.List to - * java.util.List> seems to be rejected by the - * compiler some of the time. - */ - private > List> toBaseExtensionList(final List theList) { - List> retVal = new ArrayList>(theList.size()); - retVal.addAll(theList); - return retVal; - } - private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, IBaseReference theRef, IBaseResource theResource, boolean theIncludedResource) throws XMLStreamException { String reference = determineReferenceText(theRef); @@ -673,10 +683,11 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeResourceToStreamWriterInDstu2Format(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeResourceToStreamWriterInDstu2Format(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, + BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { /* - * DSTU2 requires extensions to come in a specific spot within the encoded content - This is a bit of a messy way - * to make that happen, but hopefully this won't matter as much once we use the HL7 structures + * DSTU2 requires extensions to come in a specific spot within the encoded content - This is a bit of a messy way to make that happen, but hopefully this won't matter as much once we use the HL7 + * structures */ List preExtensionChildren = new ArrayList(); @@ -691,9 +702,9 @@ public class XmlParser extends BaseParser implements IParser { postExtensionChildren.add(next); } } - + CompositeChildElement parent = new CompositeChildElement(theResDef); - + encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, preExtensionChildren, theIncludedResource, parent); encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); @@ -703,19 +714,6 @@ public class XmlParser extends BaseParser implements IParser { } - @Override - public void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws DataFormatException { - XMLStreamWriter eventWriter; - try { - eventWriter = createXmlWriter(theWriter); - - encodeResourceToXmlStreamWriter(theResource, eventWriter, false); - eventWriter.flush(); - } catch (XMLStreamException e) { - throw new ConfigurationException("Failed to initialize STaX event factory", e); - } - } - private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theIncludedResource) throws XMLStreamException, DataFormatException { String resourceId = null; if (theResource instanceof IResource) { @@ -873,7 +871,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List> theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List> theExtensions, String tagName, boolean theIncludedResource) + throws XMLStreamException, DataFormatException { for (IBaseExtension next : theExtensions) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { continue; @@ -1005,6 +1004,11 @@ public class XmlParser extends BaseParser implements IParser { } } + @Override + public EncodingEnum getEncoding() { + return EncodingEnum.XML; + } + @Override public Bundle parseBundle(Class theResourceType, Reader theReader) { XMLEventReader streamReader = createStreamReader(theReader); @@ -1017,12 +1021,6 @@ public class XmlParser extends BaseParser implements IParser { return doXmlLoop(theStreamReader, parserState); } - @Override - public T doParseResource(Class theResourceType, Reader theReader) { - XMLEventReader streamReader = createStreamReader(theReader); - return parseResource(theResourceType, streamReader); - } - private T parseResource(Class theResourceType, XMLEventReader theStreamReader) { ParserState parserState = ParserState.getPreResourceInstance(theResourceType, myContext, false, getErrorHandler()); return doXmlLoop(theStreamReader, parserState); @@ -1042,6 +1040,16 @@ public class XmlParser extends BaseParser implements IParser { return this; } + /** + * This is just to work around the fact that casting java.util.List to java.util.List> seems to be + * rejected by the compiler some of the time. + */ + private > List> toBaseExtensionList(final List theList) { + List> retVal = new ArrayList>(theList.size()); + retVal.addAll(theList); + return retVal; + } + private void writeAtomLink(XMLStreamWriter theEventWriter, String theRel, StringDt theStringDt) throws XMLStreamException { if (StringUtils.isNotBlank(theStringDt.getValue())) { theEventWriter.writeStartElement("link"); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Elements.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Elements.java index 1d7c638c64a..1f2bb71b10f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Elements.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Elements.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.annotation; +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2015 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.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java index bc120f7eb92..f51297bdf97 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/SummaryEnum.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.rest.api; +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2015 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.util.HashMap; import java.util.Map; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index fadccc1ae9c..8d48b65ed85 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -33,6 +33,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -164,11 +165,11 @@ public abstract class BaseClient implements IRestfulClient { } T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) { - return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null); + return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null); } T invokeClient(FhirContext theContext, IClientResponseHandler binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint, - boolean theLogRequestAndResponse, SummaryEnum theSummaryMode) { + boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set theSubsetElements) { if (!myDontValidateConformance) { myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this); @@ -196,6 +197,10 @@ public abstract class BaseClient implements IRestfulClient { if (thePrettyPrint == Boolean.TRUE) { params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE)); } + + if (theSubsetElements != null && theSubsetElements.isEmpty()== false) { + params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ','))); + } EncodingEnum encoding = getEncoding(); if (theEncoding != null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 7ce5b4a13e4..406a5df05a4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -24,12 +24,15 @@ import static org.apache.commons.lang3.StringUtils.*; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -226,7 +229,7 @@ public class GenericClient extends BaseClient implements IGenericClient { return delete(theType, new IdDt(theId)); } - private T doReadOrVRead(final Class theType, IIdType theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, SummaryEnum theSummary, EncodingEnum theEncoding) { + private T doReadOrVRead(final Class theType, IIdType theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches, Boolean thePrettyPrint, SummaryEnum theSummary, EncodingEnum theEncoding, Set theSubsetElements) { String resName = toResourceName(theType); IIdType id = theId; if (!id.hasBaseUrl()) { @@ -259,10 +262,10 @@ public class GenericClient extends BaseClient implements IGenericClient { ResourceResponseHandler binding = new ResourceResponseHandler(theType, id, allowHtmlResponse); if (theNotModifiedHandler == null) { - return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary); + return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements); } else { try { - return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary); + return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements); } catch (NotModifiedException e) { return theNotModifiedHandler.call(); } @@ -413,7 +416,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public T read(final Class theType, UriDt theUrl) { IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl); - return doReadOrVRead(theType, id, false, null, null, false, null, null); + return doReadOrVRead(theType, id, false, null, null, false, null, null, null); } @Override @@ -559,7 +562,7 @@ public class GenericClient extends BaseClient implements IGenericClient { if (theId.hasVersionIdPart() == false) { throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_NO_VERSION_ID_FOR_VREAD, theId.getValue())); } - return doReadOrVRead(theType, theId, true, null, null, false, null, null); + return doReadOrVRead(theType, theId, true, null, null, false, null, null, null); } /* also deprecated in interface */ @@ -592,15 +595,9 @@ public class GenericClient extends BaseClient implements IGenericClient { protected EncodingEnum myParamEncoding; protected Boolean myPrettyPrint; private boolean myQueryLogRequestAndResponse; + private HashSet mySubsetElements; protected SummaryEnum mySummaryMode; - @SuppressWarnings("unchecked") - @Override - public T summaryMode(SummaryEnum theSummary) { - mySummaryMode = theSummary; - return ((T) this); - } - @SuppressWarnings("unchecked") @Override public T andLogRequestAndResponse(boolean theLogRequestAndResponse) { @@ -626,6 +623,10 @@ public class GenericClient extends BaseClient implements IGenericClient { return myParamEncoding; } + protected HashSet getSubsetElements() { + return mySubsetElements; + } + protected Z invoke(Map> theParams, IClientResponseHandler theHandler, BaseHttpClientInvocation theInvocation) { // if (myParamEncoding != null) { // theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType())); @@ -639,7 +640,7 @@ public class GenericClient extends BaseClient implements IGenericClient { myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint); } - Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode); + Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements); return resp; } @@ -658,6 +659,24 @@ public class GenericClient extends BaseClient implements IGenericClient { return (T) this; } + @SuppressWarnings("unchecked") + @Override + public T elementsSubset(String... theElements) { + if (theElements != null && theElements.length > 0) { + mySubsetElements = new HashSet(Arrays.asList(theElements)); + } else { + mySubsetElements = null; + } + return (T) this; + } + + @SuppressWarnings("unchecked") + @Override + public T summaryMode(SummaryEnum theSummary) { + mySummaryMode = theSummary; + return ((T) this); + } + } private final class BundleResponseHandler implements IClientResponseHandler { @@ -1489,9 +1508,9 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public Object execute() {//AAA if (myId.hasVersionIdPart()) { - return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding); + return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements()); } else { - return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding); + return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches, myPrettyPrint, mySummaryMode, myParamEncoding, getSubsetElements()); } } @@ -1634,9 +1653,9 @@ public class GenericClient extends BaseClient implements IGenericClient { private final class ResourceResponseHandler implements IClientResponseHandler { + private boolean myAllowHtmlResponse; private IIdType myId; private Class myType; - private boolean myAllowHtmlResponse; public ResourceResponseHandler(Class theType, IIdType theId) { myType = theType; @@ -1698,14 +1717,15 @@ public class GenericClient extends BaseClient implements IGenericClient { private List myInclude = new ArrayList(); private DateRangeParam myLastUpdated; private Integer myParamLimit; + private List myProfile = new ArrayList(); private String myResourceId; private String myResourceName; private Class myResourceType; private Class myReturnBundleType; private List myRevInclude = new ArrayList(); private SearchStyleEnum mySearchStyle; + private List mySecurity = new ArrayList(); private List mySort = new ArrayList(); - private List myTags = new ArrayList(); public SearchInternal() { @@ -1734,6 +1754,14 @@ public class GenericClient extends BaseClient implements IGenericClient { addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken()); } + for (TokenParam next : mySecurity) { + addParam(params, Constants.PARAM_SECURITY, next.getValueAsQueryToken()); + } + + for (String next : myProfile) { + addParam(params, Constants.PARAM_PROFILE, next); + } + for (Include next : myInclude) { addParam(params, Constants.PARAM_INCLUDE, next.getValue()); } @@ -1867,6 +1895,20 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IQuery withProfile(String theProfileUri) { + Validate.notBlank(theProfileUri, "theProfileUri must not be null or empty"); + myProfile.add(theProfileUri); + return this; + } + + @Override + public IQuery withSecurity(String theSystem, String theCode) { + Validate.notBlank(theCode, "theCode must not be null or empty"); + mySecurity.add(new TokenParam(theSystem, theCode)); + return this; + } + @Override public IQuery withTag(String theSystem, String theCode) { Validate.notBlank(theCode, "theCode must not be null or empty"); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java index daa952250e6..832ef31ad88 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IClientExecutable.java @@ -34,6 +34,13 @@ public interface IClientExecutable, Y> { @Deprecated T andLogRequestAndResponse(boolean theLogRequestAndResponse); + /** + * Request that the server return subsetted resources, containing only the elements specified in the given parameters. + * For example: subsetElements("name", "identifier") requests that the server only return + * the "name" and "identifier" fields in the returned resource, and omit any others. + */ + T elementsSubset(String... theElements); + T encodedJson(); T encodedXml(); @@ -46,5 +53,5 @@ public interface IClientExecutable, Y> { * Request that the server modify the response using the _summary param */ T summaryMode(SummaryEnum theSummary); - + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index 78b23878915..1572c706bf0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -45,6 +45,22 @@ public interface IQuery extends IClientExecutable, T>, IBaseQuery withTag(String theSystem, String theCode); + /** + * Match only resources where the resource has the given security tag. This parameter corresponds to + * the _security URL parameter. + * @param theSystem The tag code system, or null to match any code system (this may not be supported on all servers) + * @param theCode The tag code. Must not be null or empty. + */ + IQuery withSecurity(String theSystem, String theCode); + + /** + * Match only resources where the resource has the given profile declaration. This parameter corresponds to + * the _profile URL parameter. + * @param theSystem The tag code system, or null to match any code system (this may not be supported on all servers) + * @param theCode The tag code. Must not be null or empty. + */ + IQuery withProfile(String theProfileUri); + /** * Forces the query to perform the search using the given method (allowable methods are described in the * FHIR Specification Section 2.1.11) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 1213364839d..98d9e57cef9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -246,16 +246,6 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding elements = ElementsParameter.getElementsValueOrNull(theRequest); - if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) { - throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters"); - } - Set elementsAppliesTo = null; - if (elements != null && isNotBlank(myResourceName)) { - elementsAppliesTo = Collections.singleton(myResourceName); - } - // Is this request coming from a browser String uaHeader = theRequest.getServletRequest().getHeader("user-agent"); boolean requestIsBrowser = false; @@ -341,8 +331,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding includes = getRequestIncludesFromParams(params); @@ -365,7 +355,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { @@ -376,8 +366,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding theLastUpdated); - void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theCount, String theSearchId, BundleTypeEnum theBundleType, - Set theIncludes); + void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, + int theOffset, Integer theCount, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes); Bundle getDstu1Bundle(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index a400bc9a851..463160e641a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -19,7 +19,8 @@ package ca.uhn.fhir.rest.server; * limitations under the License. * #L% */ -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.lang.annotation.Annotation; @@ -62,7 +63,6 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding; -import ca.uhn.fhir.rest.method.ElementsParameter; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -452,9 +452,7 @@ public class RestfulServer extends HttpServlet { boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest()); Set summaryMode = RestfulServerUtils.determineSummaryMode(theRequest); boolean respondGzip = theRequest.isRespondGzip(); - Set elements = ElementsParameter.getElementsValueOrNull(theRequest); - Set elementsAppliesTo = null; // TODO: persist this across pages - + IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory(); Set includes = new HashSet(); @@ -480,7 +478,7 @@ public class RestfulServer extends HttpServlet { return; } } - RestfulServerUtils.streamResponseAsBundle(this, theResponse, bundle, responseEncoding, theRequest.getFhirServerBase(), prettyPrint, summaryMode, respondGzip, requestIsBrowser); + RestfulServerUtils.streamResponseAsBundle(this, theResponse, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest); } else { IBaseResource resBundle = bundleFactory.getResourceBundle(); for (int i = getInterceptors().size() - 1; i >= 0; i--) { @@ -491,8 +489,7 @@ public class RestfulServer extends HttpServlet { return; } } - RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, summaryMode, Constants.STATUS_HTTP_200_OK, - theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false, elements, elementsAppliesTo); + RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), false, theRequest); } } @@ -678,14 +675,12 @@ public class RestfulServer extends HttpServlet { return; } } - + /* - * Actualy invoke the server method. This call is to a HAPI method - * binding, which is an object that wraps a specific implementing (user-supplied) - * method, but handles its input and provides its output back to the client. + * Actualy invoke the server method. This call is to a HAPI method binding, which is an object that wraps a specific implementing (user-supplied) method, but handles its input and provides + * its output back to the client. * - * This is basically the end of processing for a successful request, - * since the method binding replies to the client and closes the response. + * This is basically the end of processing for a successful request, since the method binding replies to the client and closes the response. */ resourceMethod.invokeServer(this, requestDetails); @@ -719,13 +714,10 @@ public class RestfulServer extends HttpServlet { } catch (Throwable e) { /* - * We have caught an exception during request processing. This might be - * because a handling method threw something they wanted to throw (e.g. - * UnprocessableEntityException because the request had business requirement - * problems) or it could be due to bugs (e.g. NullPointerException). + * We have caught an exception during request processing. This might be because a handling method threw something they wanted to throw (e.g. UnprocessableEntityException because the request + * had business requirement problems) or it could be due to bugs (e.g. NullPointerException). * - * First we let the interceptors have a crack at converting the exception - * into something HAPI can use (BaseServerResponseException) + * First we let the interceptors have a crack at converting the exception into something HAPI can use (BaseServerResponseException) */ BaseServerResponseException exception = null; for (int i = getInterceptors().size() - 1; i >= 0; i--) { @@ -738,9 +730,8 @@ public class RestfulServer extends HttpServlet { } /* - * If none of the interceptors converted the exception, default behaviour is to - * keep the exception as-is if it extends BaseServerResponseException, otherwise - * wrap it in an InternalErrorException. + * If none of the interceptors converted the exception, default behaviour is to keep the exception as-is if it extends BaseServerResponseException, otherwise wrap it in an + * InternalErrorException. */ if (exception == null) { exception = DEFAULT_EXCEPTION_HANDLER.preProcessOutgoingException(requestDetails, e, theRequest); @@ -758,8 +749,13 @@ public class RestfulServer extends HttpServlet { } /* - * If nobody handles it, default behaviour is to stream back the - * OperationOutcome to the client. + * If we're handling an exception, no summary mode should be applied + */ + requestDetails.getParameters().remove(Constants.PARAM_SUMMARY); + requestDetails.getParameters().remove(Constants.PARAM_ELEMENTS); + + /* + * If nobody handles it, default behaviour is to stream back the OperationOutcome to the client. */ DEFAULT_EXCEPTION_HANDLER.handleException(requestDetails, exception, theRequest, theResponse); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index bd04620e329..c77f00f524d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -63,9 +63,11 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.method.ElementsParameter; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SummaryEnumParameter; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class RestfulServerUtils { static final Pattern ACCEPT_HEADER_PATTERN = Pattern.compile("\\s*([a-zA-Z0-9+.*/-]+)\\s*(;\\s*([a-zA-Z]+)\\s*=\\s*([a-zA-Z0-9.]+)\\s*)?(,?)"); @@ -285,9 +287,19 @@ public class RestfulServerUtils { return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT); } - public static IParser getNewParser(FhirContext theContext, EncodingEnum theResponseEncoding, boolean thePrettyPrint, Set theSummaryMode) { + public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) { + + // Pretty print + boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails); + + // Determine response encoding + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails.getServletRequest()); + if (responseEncoding == null) { + responseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding(); + } + IParser parser; - switch (theResponseEncoding) { + switch (responseEncoding) { case JSON: parser = theContext.newJsonParser(); break; @@ -296,17 +308,41 @@ public class RestfulServerUtils { parser = theContext.newXmlParser(); break; } - parser.setPrettyPrint(thePrettyPrint); - if (theSummaryMode != null) { - if (theSummaryMode.contains(SummaryEnum.COUNT)) { + parser.setPrettyPrint(prettyPrint); + parser.setServerBaseUrl(theRequestDetails.getFhirServerBase()); + + // Summary mode + Set summaryMode = RestfulServerUtils.determineSummaryMode(theRequestDetails); + + // _elements + Set elements = ElementsParameter.getElementsValueOrNull(theRequestDetails); + if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) { + throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters"); + } + Set elementsAppliesTo = null; + if (elements != null && isNotBlank(theRequestDetails.getResourceName())) { + elementsAppliesTo = Collections.singleton(theRequestDetails.getResourceName()); + } + + if (summaryMode != null) { + if (summaryMode.contains(SummaryEnum.COUNT)) { parser.setEncodeElements(Collections.singleton("Bundle.total")); - } else if (theSummaryMode.contains(SummaryEnum.TEXT)) { + } else if (summaryMode.contains(SummaryEnum.TEXT)) { parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); } else { - parser.setSuppressNarratives(theSummaryMode.contains(SummaryEnum.DATA)); - parser.setSummaryMode(theSummaryMode.contains(SummaryEnum.TRUE)); + parser.setSuppressNarratives(summaryMode.contains(SummaryEnum.DATA)); + parser.setSummaryMode(summaryMode.contains(SummaryEnum.TRUE)); } } + if (elements != null && elements.size() > 0) { + Set newElements = new HashSet(); + for (String next : elements) { + newElements.add("*." + next); + } + parser.setEncodeElements(newElements); + parser.setEncodeElementsAppliesToResourceTypes(elementsAppliesTo); + } + return parser; } @@ -378,13 +414,15 @@ public class RestfulServerUtils { return prettyPrint; } - public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, Set theSummaryMode, boolean theRespondGzip, boolean theRequestIsBrowser) + public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, String theServerBase, Set theSummaryMode, boolean theRespondGzip, boolean theRequestIsBrowser, RequestDetails theRequestDetails) throws IOException { assert!theServerBase.endsWith("/"); theHttpResponse.setStatus(200); - EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding(); + // Determine response encoding + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails.getServletRequest()); + responseEncoding = responseEncoding != null ? responseEncoding : theServer.getDefaultResponseEncoding(); if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) { theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType()); @@ -398,8 +436,7 @@ public class RestfulServerUtils { Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip); try { - IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theSummaryMode); - parser.setServerBaseUrl(theServerBase); + IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), theRequestDetails); if (theSummaryMode.contains(SummaryEnum.TEXT)) { parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); } @@ -409,13 +446,17 @@ public class RestfulServerUtils { } } - public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, Set theNarrativeMode, int stausCode, boolean theRespondGzip, - String theServerBase, boolean theAddContentLocationHeader, Set theElements, Set theElementsAppliesTo) throws IOException { + public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, boolean theRequestIsBrowser, Set theSummaryMode, int stausCode, boolean theRespondGzip, + boolean theAddContentLocationHeader, RequestDetails theRequestDetails) throws IOException { theHttpResponse.setStatus(stausCode); - if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() && isNotBlank(theServerBase)) { + // Determine response encoding + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails.getServletRequest()); + + String serverBase = theRequestDetails.getFhirServerBase(); + if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() && isNotBlank(serverBase)) { String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName(); - IIdType fullId = theResource.getIdElement().withServerBase(theServerBase, resName); + IIdType fullId = theResource.getIdElement().withServerBase(serverBase, resName); theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue()); } @@ -428,11 +469,11 @@ public class RestfulServerUtils { if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) { RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource); if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) { - addProfileToBundleEntry(theServer.getFhirContext(), theResource, theServerBase); + addProfileToBundleEntry(theServer.getFhirContext(), theResource, serverBase); } } - if (theResource instanceof IBaseBinary && theResponseEncoding == null) { + if (theResource instanceof IBaseBinary && responseEncoding == null) { IBaseBinary bin = (IBaseBinary) theResource; if (isNotBlank(bin.getContentType())) { theHttpResponse.setContentType(bin.getContentType()); @@ -454,9 +495,10 @@ public class RestfulServerUtils { return; } - EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding(); - - boolean encodingDomainResourceAsText = theNarrativeMode.contains(SummaryEnum.TEXT); + // Ok, we're not serving a binary resource, so apply default encoding + responseEncoding = responseEncoding != null ? responseEncoding : theServer.getDefaultResponseEncoding(); + + boolean encodingDomainResourceAsText = theSummaryMode.contains(SummaryEnum.TEXT); if (encodingDomainResourceAsText) { /* * If the user requests "text" for a bundle, only suppress the non text elements in the Element.entry.resource @@ -504,16 +546,7 @@ public class RestfulServerUtils { if (encodingDomainResourceAsText && theResource instanceof IResource) { writer.append(((IResource) theResource).getText().getDiv().getValueAsString()); } else { - IParser parser = getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode); - parser.setServerBaseUrl(theServerBase); - if (theElements != null && theElements.size() > 0) { - Set elements = new HashSet(); - for (String next : theElements) { - elements.add("*." + next); - } - parser.setEncodeElements(elements); - parser.setEncodeElementsAppliesToResourceTypes(theElementsAppliesTo); - } + IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails); parser.encodeResourceToWriter(theResource, writer); } } finally { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java index 0c7b83ded32..5b469f01259 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java @@ -19,8 +19,7 @@ package ca.uhn.fhir.rest.server.interceptor; * limitations under the License. * #L% */ - -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.util.Collections; @@ -37,7 +36,6 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.method.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -73,9 +71,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter { } } - boolean requestIsBrowser = RestfulServer.requestIsBrowser(theRequest); - String fhirServerBase = theRequestDetails.getFhirServerBase(); - RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, RestfulServerUtils.determineResponseEncodingNoDefault(theRequest), true, requestIsBrowser, Collections.singleton(SummaryEnum.FALSE), statusCode, false, fhirServerBase, false, null, null); + RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false, theRequestDetails); // theResponse.setStatus(statusCode); // theRequestDetails.getServer().addHeadersToResponse(theResponse); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java index b1287e3b21c..e3d7e9b9e15 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java @@ -186,29 +186,17 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse); } - streamResponse(theRequestDetails, theServletRequest, theServletResponse, theResponseObject); + streamResponse(theRequestDetails, theServletResponse, theResponseObject); return false; } - private void streamResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse, IBaseResource resource) { - // Pretty print - boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails); - - // Determine response encoding - EncodingEnum responseEncoding = null; - if (theRequestDetails.getParameters().containsKey(Constants.PARAM_FORMAT)) { - // Browsers often state that they accept XML but we won't take that as being the user's preference - // unless they explicitly request it - responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theServletRequest); - } - if (responseEncoding == null) { - responseEncoding = theRequestDetails.getServer().getDefaultResponseEncoding(); - } - - IParser p = responseEncoding.newParser(theRequestDetails.getServer().getFhirContext()); - p.setPrettyPrint(prettyPrint); + private void streamResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, IBaseResource resource) { + IParser p = RestfulServerUtils.getNewParser(theRequestDetails.getServer().getFhirContext(), theRequestDetails); + + EncodingEnum encoding = p.getEncoding(); + String encoded = p.encodeResourceToString(resource); theServletResponse.setContentType(Constants.CT_HTML_WITH_UTF8); @@ -239,7 +227,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { " \n" + "\n" + " " + - "
" + format(encoded, responseEncoding) + "
" + + "
" + format(encoded, encoding) + "
" + " " + ""; //@formatter:off @@ -277,16 +265,11 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); } - if (!(theException instanceof BaseServerResponseException)) { + if (theException.getOperationOutcome() == null) { return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); } - BaseServerResponseException bsre = (BaseServerResponseException)theException; - if (bsre.getOperationOutcome() == null) { - return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); - } - - streamResponse(theRequestDetails, theServletRequest, theServletResponse, bsre.getOperationOutcome()); + streamResponse(theRequestDetails, theServletResponse, theException.getOperationOutcome()); return false; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java index 5f292b1710d..aa5e3361a1a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.dao; +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2015 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.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java index d415c8ef845..f4a3359d1a8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.dao; +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2015 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.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java index 7123da8a607..fd5ea50e45d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.provider; +/* + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2015 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.isNotBlank; import javax.servlet.http.HttpServletRequest; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java index a0173620d26..51816502c77 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2Test.java @@ -656,6 +656,24 @@ public class FhirSystemDaoDstu2Test extends BaseJpaTest { assertEquals("Patient/temp6789", p.getLink().get(0).getOther().getReference().getValue()); } + @Test + public void testTransactionFromBundleJosh() throws Exception { + + InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/josh-bundle.json"); + String bundleStr = IOUtils.toString(bundleRes); + Bundle bundle = ourFhirContext.newJsonParser().parseResource(Bundle.class, bundleStr); + + Bundle resp = ourSystemDao.transaction(bundle); + + ourLog.info(ourFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(resp)); + + OperationOutcome oo = (OperationOutcome) resp.getEntry().get(0).getResource(); + assertThat(oo.getIssue().get(0).getDiagnostics(), containsString("Transaction completed")); + + assertEquals("201 Created", resp.getEntry().get(1).getResponse().getStatus()); + assertEquals("201 Created", resp.getEntry().get(2).getResponse().getStatus()); + } + @Test public void testTransactionReadAndSearch() { String methodName = "testTransactionReadAndSearch"; diff --git a/hapi-fhir-jpaserver-base/src/test/resources/josh-bundle.json b/hapi-fhir-jpaserver-base/src/test/resources/josh-bundle.json new file mode 100644 index 00000000000..80fd4ed8dba --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/resources/josh-bundle.json @@ -0,0 +1,39 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "fullUrl": "urn:oid:1", + "request": { + "method": "POST", + "url": "Patient" + }, + "resource": { + "resourceType": "Patient", + "gender": "female", + "birthDate": "1960-09-13" + } + }, + { + "fullUrl": "urn:oid:2", + "request": { + "method": "POST", + "url": "Appointment" + }, + "resource": { + "resourceType": "Appointment", + "status": "booked", + "start": "2015-08-01T09:00:00Z", + "end": "2015-08-01T09:20:00Z", + "participant": [ + { + "actor": { + "reference": "urn:oid:1" + }, + "status": "accepted" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index dfae399483a..a2686b6b543 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -133,7 +133,7 @@ public class GenericClientTest { ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")}); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); @@ -149,30 +149,6 @@ public class GenericClientTest { } - @Test - public void testMissing() throws Exception { - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22")}); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return (new ReaderInputStream(new StringReader(getPatientFeedWithOneResult()), Charset.forName("UTF-8"))); - }}); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - - client.search().forResource("Patient").where(Patient.NAME.isMissing(true)).execute(); - assertEquals("http://example.com/fhir/Patient?name%3Amissing=true", capt.getValue().getRequestLine().getUri()); - - client.search().forResource("Patient").where(Patient.NAME.isMissing(false)).execute(); - assertEquals("http://example.com/fhir/Patient?name%3Amissing=false", capt.getValue().getRequestLine().getUri()); - } - @Test public void testCreateWithStringAutoDetectsEncoding() throws Exception { @@ -400,6 +376,86 @@ public class GenericClientTest { } + @Test + public void testHistory() throws Exception { + + final String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + Bundle response; + + //@formatter:off + response = client + .history() + .onServer() + .andReturnDstu1Bundle() + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.size()); + idx++; + + //@formatter:off + response = client + .history() + .onType(Patient.class) + .andReturnDstu1Bundle() + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.size()); + idx++; + + //@formatter:off + response = client + .history() + .onInstance(new IdDt("Patient", "123")) + .andReturnDstu1Bundle() + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient/123/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.size()); + idx++; + } + + @Test + public void testMissing() throws Exception { + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return (new ReaderInputStream(new StringReader(getPatientFeedWithOneResult()), Charset.forName("UTF-8"))); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + + client.search().forResource("Patient").where(Patient.NAME.isMissing(true)).execute(); + assertEquals("http://example.com/fhir/Patient?name%3Amissing=true", capt.getValue().getRequestLine().getUri()); + + client.search().forResource("Patient").where(Patient.NAME.isMissing(false)).execute(); + assertEquals("http://example.com/fhir/Patient?name%3Amissing=false", capt.getValue().getRequestLine().getUri()); + } + @Test public void testRead() throws Exception { @@ -438,32 +494,6 @@ public class GenericClientTest { } - @Test - public void testSetDefaultEncoding() throws Exception { - - String msg = ourCtx.newJsonParser().encodeResourceToString(new Patient()); - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); -// Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"), -// new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), -// new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; label=\"Some tag\"") }; -// when(myHttpResponse.getAllHeaders()).thenReturn(headers); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - (client).setEncoding(EncodingEnum.JSON); - int count = 0; - - client.read(Patient.class, new IdDt("Patient/1234")); - assertEquals("http://example.com/fhir/Patient/1234?_format=json", capt.getAllValues().get(count).getURI().toString()); - count++; - - } - @Test public void testReadFluent() throws Exception { @@ -482,7 +512,7 @@ public class GenericClientTest { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); int count = 0; - + Patient response = client.read().resource(Patient.class).withId(new IdDt("Patient/1234")).execute(); assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal")); assertEquals("http://example.com/fhir/Patient/1234", capt.getAllValues().get(count++).getURI().toString()); @@ -509,7 +539,6 @@ public class GenericClientTest { } - @Test public void testReadWithAbsoluteUrl() throws Exception { @@ -698,117 +727,6 @@ public class GenericClientTest { } - @SuppressWarnings("unused") - @Test - public void testSearchByTag() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource(Patient.class) - .withTag("urn:foo", "123") - .withTag("urn:bar", "456") - .execute(); - //@formatter:on - - assertEquals( - "http://example.com/fhir/Patient?_tag=urn%3Afoo%7C123&_tag=urn%3Abar%7C456", - capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchWithReverseInclude() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource(Patient.class) - .encodedJson() - .revInclude(Provenance.INCLUDE_TARGET) - .execute(); - //@formatter:on - - assertEquals( - "http://example.com/fhir/Patient?_revinclude=Provenance.target&_format=json", - capt.getValue().getURI().toString()); - - } - - @Test - public void testHistory() throws Exception { - - final String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public InputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - }}); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - int idx = 0; - Bundle response; - - //@formatter:off - response = client - .history() - .onServer() - .andReturnDstu1Bundle() - .execute(); - //@formatter:on - assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); - assertEquals(1, response.size()); - idx++; - - //@formatter:off - response = client - .history() - .onType(Patient.class) - .andReturnDstu1Bundle() - .execute(); - //@formatter:on - assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString()); - assertEquals(1, response.size()); - idx++; - - //@formatter:off - response = client - .history() - .onInstance(new IdDt("Patient", "123")) - .andReturnDstu1Bundle() - .execute(); - //@formatter:on - assertEquals("http://example.com/fhir/Patient/123/_history", capt.getAllValues().get(idx).getURI().toString()); - assertEquals(1, response.size()); - idx++; - } - - @SuppressWarnings("unused") @Test public void testSearchByNumberExact() throws Exception { @@ -834,6 +752,32 @@ public class GenericClientTest { } + @SuppressWarnings("unused") + @Test + public void testSearchByProfile() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .withProfile("http://1") + .withProfile("http://2") + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?_profile=http%3A%2F%2F1&_profile=http%3A%2F%2F2", capt.getValue().getURI().toString()); + + } + @SuppressWarnings("unused") @Test public void testSearchByQuantity() throws Exception { @@ -909,6 +853,32 @@ public class GenericClientTest { } + @SuppressWarnings("unused") + @Test + public void testSearchBySecurity() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .withSecurity("urn:foo", "123") + .withSecurity("urn:bar", "456") + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?_security=urn%3Afoo%7C123&_security=urn%3Abar%7C456", capt.getValue().getURI().toString()); + + } + @SuppressWarnings("unused") @Test public void testSearchByString() throws Exception { @@ -969,6 +939,32 @@ public class GenericClientTest { } + @SuppressWarnings("unused") + @Test + public void testSearchByTag() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .withTag("urn:foo", "123") + .withTag("urn:bar", "456") + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?_tag=urn%3Afoo%7C123&_tag=urn%3Abar%7C456", capt.getValue().getURI().toString()); + + } + @SuppressWarnings("unused") @Test public void testSearchByToken() throws Exception { @@ -1030,7 +1026,8 @@ public class GenericClientTest { @Override public InputStream answer(InvocationOnMock theInvocation) throws Throwable { return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); - }}); + } + }); IGenericClient client = ourCtx.newRestfulGenericClient("http://foo"); int index = 0; @@ -1133,9 +1130,8 @@ public class GenericClientTest { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - Bundle response = client - .search(new UriDt( - "http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json")); + Bundle response = client.search(new UriDt( + "http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json")); assertEquals( "http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json", @@ -1157,10 +1153,8 @@ public class GenericClientTest { IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - Bundle response = client - .search(Patient.class, - new UriDt( - "http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json")); + Bundle response = client.search(Patient.class, new UriDt( + "http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json")); assertEquals( "http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json", @@ -1242,6 +1236,58 @@ public class GenericClientTest { } + @SuppressWarnings("unused") + @Test + public void testSearchWithReverseInclude() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .encodedJson() + .revInclude(Provenance.INCLUDE_TARGET) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?_revinclude=Provenance.target&_format=json", capt.getValue().getURI().toString()); + + } + + @Test + public void testSetDefaultEncoding() throws Exception { + + String msg = ourCtx.newJsonParser().encodeResourceToString(new Patient()); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + // Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"), + // new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"), + // new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; label=\"Some tag\"") }; + // when(myHttpResponse.getAllHeaders()).thenReturn(headers); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + (client).setEncoding(EncodingEnum.JSON); + int count = 0; + + client.read(Patient.class, new IdDt("Patient/1234")); + assertEquals("http://example.com/fhir/Patient/1234?_format=json", capt.getAllValues().get(count).getURI().toString()); + count++; + + } + @Test public void testTransaction() throws Exception { String bundleStr = IOUtils.toString(getClass().getResourceAsStream("/bundle.json")); @@ -1333,7 +1379,7 @@ public class GenericClientTest { assertEquals(1, capt.getAllValues().size()); assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType()+Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); count++; MethodOutcome outcome = client.update().resource(p1).execute(); @@ -1348,7 +1394,7 @@ public class GenericClientTest { assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH); assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue()); assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length); - assertEquals(EncodingEnum.XML.getResourceContentType()+Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); + assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); /* * Try fluent options diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ExceptionTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ExceptionTest.java index 9655deee899..1310b879b37 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ExceptionTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ExceptionTest.java @@ -1,17 +1,15 @@ package ca.uhn.fhir.rest.server; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import java.util.List; import java.util.concurrent.TimeUnit; -import javax.security.sasl.AuthorizeCallback; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import junit.framework.AssertionFailedError; - import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -46,6 +44,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; import ca.uhn.fhir.util.PortUtil; +import junit.framework.AssertionFailedError; /** * Created by dsotnikov on 2/25/2014. diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java index b0d43ef9e97..7e6681a661d 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu2Test.java @@ -1,6 +1,8 @@ package ca.uhn.fhir.rest.client; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.either; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -1053,6 +1055,35 @@ public class GenericClientDstu2Test { } + @Test + public void testSearchWithProfileAndSecurity() throws Exception { + String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .withProfile("http://foo1") + .withProfile("http://foo2") + .withSecurity("system1", "code1") + .withSecurity("system2", "code2") + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?_security=system1%7Ccode1&_security=system2%7Ccode2&_profile=http%3A%2F%2Ffoo1&_profile=http%3A%2F%2Ffoo2", capt.getValue().getURI().toString()); + assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass()); + + } + + + @Test public void testSearchWithSummaryParam() throws Exception { String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; @@ -1078,6 +1109,57 @@ public class GenericClientDstu2Test { } + @Test + public void testSearchWithElementsParam() throws Exception { + String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .elementsSubset("name", "identifier") + .execute(); + //@formatter:on + + assertThat(capt.getValue().getURI().toString(), either(equalTo("http://example.com/fhir/Patient?name=james&_elements=name%2Cidentifier")).or(equalTo("http://example.com/fhir/Patient?name=james&_elements=identifier%2Cname"))); + assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass()); + + } + + @Test + public void testReadWithElementsParam() throws Exception { + String msg = "{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}"; + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + IBaseResource response = client.read() + .resource("Patient") + .withId("123") + .elementsSubset("name", "identifier") + .execute(); + //@formatter:on + + assertThat(capt.getValue().getURI().toString(), either(equalTo("http://example.com/fhir/Patient/123?_elements=name%2Cidentifier")).or(equalTo("http://example.com/fhir/Patient/123?_elements=identifier%2Cname"))); + assertEquals(Patient.class, response.getClass()); + + } + + @Test public void testReadWithSummaryParamHtml() throws Exception { String msg = "
HELP IM A DIV
"; diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java index 33101942ea2..13f46b99dbe 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java @@ -134,6 +134,19 @@ public class ResponseHighlightingInterceptorTest { } + @Test + public void testSearchWithSummaryParam() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithWildcardRetVal&_summary=count"); + httpGet.addHeader("Accept", "html"); + CloseableHttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + + ourLog.info("Resp: {}", responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + assertThat(responseContent, not(containsString("entry"))); + } + @Test public void testGetInvalidResource() throws Exception { HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Foobar/123"); diff --git a/pom.xml b/pom.xml index 19c1a7b0006..5c485a01e8f 100644 --- a/pom.xml +++ b/pom.xml @@ -251,6 +251,11 @@ javax.servlet-api 3.1.0 + + lt.velykis.maven.skins + reflow-velocity-tools + 1.1.1 + org.apache.commons commons-dbcp2 @@ -281,6 +286,36 @@ httpcore 4.4 + + org.apache.maven.doxia + doxia-module-markdown + 1.6 + + + org.apache.maven.scm + maven-scm-api + 1.9 + + + org.apache.maven.scm + maven-scm-manager-plexus + 1.9 + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9 + + + org.apache.maven.wagon + wagon-scm + 2.2 + + + org.apache.velocity + velocity + 1.7 + org.eclipse.jetty jetty-http @@ -463,38 +498,30 @@ org.apache.maven.wagon wagon-scm - 2.2 org.apache.maven.scm maven-scm-manager-plexus - 1.9 org.apache.maven.scm maven-scm-provider-gitexe - 1.9 org.apache.maven.scm maven-scm-api - 1.9 org.apache.maven.doxia doxia-module-markdown - 1.6 - lt.velykis.maven.skins reflow-velocity-tools - 1.1.1 org.apache.velocity velocity - 1.7 @@ -866,6 +893,43 @@ + + + org.apache.maven.wagon + wagon-scm + 2.2 + + + org.apache.maven.scm + maven-scm-manager-plexus + 1.9 + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9 + + + org.apache.maven.scm + maven-scm-api + 1.9 + + + org.apache.maven.doxia + doxia-module-markdown + 1.6 + + + lt.velykis.maven.skins + reflow-velocity-tools + 1.1.1 + + + org.apache.velocity + velocity + 1.7 + + org.apache.maven.plugins diff --git a/src/site/xdoc/doc_rest_client.xml b/src/site/xdoc/doc_rest_client.xml index d164031d706..0ede4a73370 100644 --- a/src/site/xdoc/doc_rest_client.xml +++ b/src/site/xdoc/doc_rest_client.xml @@ -223,6 +223,33 @@ + +

Search - Subsetting (_summary and _elements)

+

+ Sometimes you may want to only ask the server to include some parts of returned + resources (instead of the whole resource). Typically this is for performance or + optimization reasons, but there may also be privacy reasons for doing this. +

+

+ To request that the server return only "summary" elements (those elements + defined in the specification with the "Σ" flag), you can use the + summaryMode(SummaryEnum) qualifier: +

+ + + + +

+ To request that the server return only elements from a custom list + provided by the client, you can use the elementsSubset(String...) + qualifier: +

+ + + + diff --git a/src/site/xdoc/doc_rest_server.xml b/src/site/xdoc/doc_rest_server.xml index 4d9bb184f01..bdf36673ca0 100644 --- a/src/site/xdoc/doc_rest_server.xml +++ b/src/site/xdoc/doc_rest_server.xml @@ -379,8 +379,8 @@ - - + +
ParameterDescriptionParameterDescription