diff --git a/examples/src/main/java/example/ExampleProviders.java b/examples/src/main/java/example/ExampleProviders.java
index a55c5b30610..2b22983972e 100644
--- a/examples/src/main/java/example/ExampleProviders.java
+++ b/examples/src/main/java/example/ExampleProviders.java
@@ -8,6 +8,7 @@ import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
+import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
@@ -36,6 +37,9 @@ public class PlainProvider {
//START SNIPPET: plainProviderServer
public class ExampleServlet extends RestfulServer {
+ /**
+ * Constructor
+ */
public ExampleServlet() {
/*
* Plain providers are passed to the server in the same way
@@ -54,5 +58,25 @@ public class ExampleServlet extends RestfulServer {
}
//END SNIPPET: plainProviderServer
+ //START SNIPPET: addressStrategy
+ public class MyServlet extends RestfulServer {
+
+ /**
+ * Constructor
+ */
+ public MyServlet() {
+
+ String serverBaseUrl = "http://foo.com/fhir";
+ setServerAddressStrategy(new HardcodedServerAddressStrategy(serverBaseUrl));
+
+ // ...add some resource providers, etc...
+ List
+ * Note that before HAPI 0.9 this method returned a {@link StringDt} but as of
+ * HAPI 0.9 this method returns a plain string. This was changed because it does not make sense to use a StringDt here
+ * since the URL itself can not contain extensions and it was therefore misleading.
+ *
> List> toBaseExtensionList(final List theList) { + List> retVal = new ArrayList >(theList.size()); + retVal.addAll(theList); + return retVal; } private void encodeResourceReferenceToStreamWriter(XMLStreamWriter theEventWriter, BaseResourceReferenceDt theRef) throws XMLStreamException { @@ -636,12 +661,12 @@ public class XmlParser extends BaseParser implements IParser { postExtensionChildren.add(next); } } - encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, preExtensionChildren, theIncludedResource); + encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, preExtensionChildren, theIncludedResource); - encodeExtensionsIfPresent(theResDef, theResource, theEventWriter, theElement, theIncludedResource); - encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, resDef.getExtensions(), theIncludedResource); + encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); + encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, resDef.getExtensions(), theIncludedResource); - encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theElement, theEventWriter, postExtensionChildren, theIncludedResource); + encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, postExtensionChildren, theIncludedResource); } @@ -701,54 +726,70 @@ public class XmlParser extends BaseParser implements IParser { theEventWriter.writeStartElement(resDef.getName()); theEventWriter.writeDefaultNamespace(FHIR_NS); - if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + if (theResource instanceof IAnyResource) { - // DSTU2+ - IResource resource = (IResource) theResource; - writeOptionalTagWithValue(theEventWriter, "id", theResourceId); - - InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - IdDt resourceId = resource.getId(); - if (resourceId != null && isNotBlank(resourceId.getVersionIdPart()) || (updated != null && !updated.isEmpty())) { - theEventWriter.writeStartElement("meta"); - String versionIdPart = resourceId.getVersionIdPart(); - if (isBlank(versionIdPart)) { - versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); - } - writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart); - if (updated != null) { - writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); - } - theEventWriter.writeEndElement(); - } + // HL7.org Structures + encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, resDef, theContainedResource); } else { - // DSTU1 - if (theResourceId != null && theContainedResource) { - theEventWriter.writeAttribute("id", theResourceId); - } - - } - - if (theResource instanceof BaseBinary) { - BaseBinary bin = (BaseBinary) theResource; - if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { - writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); - writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); - } else { - if (bin.getContentType() != null) { - theEventWriter.writeAttribute("contentType", bin.getContentType()); - } - theEventWriter.writeCharacters(bin.getContentAsBase64()); - } - } else { if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + // DSTU2+ - encodeResourceToStreamWriterInDstu2Format(resDef, theResource, theResource, theEventWriter, resDef, theContainedResource); + + IResource resource = (IResource) theResource; + writeOptionalTagWithValue(theEventWriter, "id", theResourceId); + + InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + IdDt resourceId = resource.getId(); + List securityLabels = ResourceMetadataKeyEnum.SECURITY_LABELS.get(resource); + if (securityLabels == null) { + securityLabels = Collections.emptyList(); + } + if ((resourceId != null && isNotBlank(resourceId.getVersionIdPart())) || (updated != null && !updated.isEmpty()) || !securityLabels.isEmpty()) { + theEventWriter.writeStartElement("meta"); + String versionIdPart = resourceId.getVersionIdPart(); + if (isBlank(versionIdPart)) { + versionIdPart = ResourceMetadataKeyEnum.VERSION.get(resource); + } + writeOptionalTagWithValue(theEventWriter, "versionId", versionIdPart); + if (updated != null) { + writeOptionalTagWithValue(theEventWriter, "lastUpdated", updated.getValueAsString()); + } + for (BaseCodingDt securityLabel : securityLabels) { + theEventWriter.writeStartElement("security"); + BaseRuntimeElementCompositeDefinition> def = (BaseRuntimeElementCompositeDefinition>) myContext.getElementDefinition(securityLabel.getClass()); + encodeCompositeElementChildrenToStreamWriter(resource, securityLabel, theEventWriter, def.getChildren(), theContainedResource); + theEventWriter.writeEndElement(); + } + theEventWriter.writeEndElement(); + } + + if (theResource instanceof BaseBinary) { + BaseBinary bin = (BaseBinary) theResource; + writeOptionalTagWithValue(theEventWriter, "contentType", bin.getContentType()); + writeOptionalTagWithValue(theEventWriter, "content", bin.getContentAsBase64()); + } else { + encodeResourceToStreamWriterInDstu2Format(resDef, theResource, theResource, theEventWriter, resDef, theContainedResource); + } + } else { + // DSTU1 - encodeCompositeElementToStreamWriter(resDef, theResource, theResource, theEventWriter, resDef, theContainedResource); + if (theResourceId != null && theContainedResource) { + theEventWriter.writeAttribute("id", theResourceId); + } + + if (theResource instanceof BaseBinary) { + BaseBinary bin = (BaseBinary) theResource; + if (bin.getContentType() != null) { + theEventWriter.writeAttribute("contentType", bin.getContentType()); + } + theEventWriter.writeCharacters(bin.getContentAsBase64()); + } else { + encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, resDef, theContainedResource); + } + } } @@ -787,24 +828,19 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeUndeclaredExtensions(RuntimeResourceDefinition theResDef, IBaseResource theResource, XMLStreamWriter theWriter, List theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { - for (ExtensionDt next : theExtensions) { + private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List extends IBaseExtension>> theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + for (IBaseExtension> next : theExtensions) { + if (next == null) { + continue; + } + theWriter.writeStartElement(tagName); - theWriter.writeAttribute("url", next.getUrl().getValue()); + + String url = next.getUrl(); + theWriter.writeAttribute("url", url); if (next.getValue() != null) { - IElement value = next.getValue(); - // RuntimeChildUndeclaredExtensionDefinition extDef = - // myContext.getRuntimeChildUndeclaredExtensionDefinition(); - // String childName = extDef.getChildNameByDatatype(nextValue.getClass()); - // if (childName == null) { - // throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + - // nextValue.getClass().getCanonicalName()); - // } - // BaseRuntimeElementDefinition> childDef = - // extDef.getChildElementDefinitionByDatatype(nextValue.getClass()); - // - // + IBaseDatatype value = next.getValue(); RuntimeChildUndeclaredExtensionDefinition extDef = myContext.getRuntimeChildUndeclaredExtensionDefinition(); String childName = extDef.getChildNameByDatatype(value.getClass()); BaseRuntimeElementDefinition> childDef; @@ -818,11 +854,11 @@ public class XmlParser extends BaseParser implements IParser { } else { childDef = extDef.getChildElementDefinitionByDatatype(value.getClass()); } - encodeChildElementToStreamWriter(theResDef, theResource, theWriter, value, childName, childDef, null, theIncludedResource); + encodeChildElementToStreamWriter(theResource, theWriter, value, childName, childDef, null, theIncludedResource); } // child extensions - encodeExtensionsIfPresent(theResDef, theResource, theWriter, next, theIncludedResource); + encodeExtensionsIfPresent(theResource, theWriter, next, theIncludedResource); theWriter.writeEndElement(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.java new file mode 100644 index 00000000000..39bf0211ae9 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.java @@ -0,0 +1,32 @@ +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; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface ConditionalOperationParam { + // just a marker +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java index 2bc08de2549..4716262608c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java @@ -131,6 +131,10 @@ public class MethodOutcome { return myVersionId; } + /** + * This will be set to {@link Boolean#TRUE} for instance of MethodOutcome which are + * returned to client instances, if the server has responded with an HTTP 201 Created. + */ public Boolean getCreated() { return myCreated; } @@ -146,8 +150,9 @@ public class MethodOutcome { * If not null, indicates whether the resource was created (as opposed to being updated). This is generally not needed, since the server can assume based on the method being called * whether the result was a creation or an update. However, it can be useful if you are implementing an update method that does a create if the ID doesn't already exist. */ - public void setCreated(Boolean theCreated) { + public MethodOutcome setCreated(Boolean theCreated) { myCreated = theCreated; + return this; } /** 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 bf46e32ef0b..6a17c587097 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 @@ -182,9 +182,14 @@ public abstract class BaseClient { } try { - ContentType ct = ContentType.get(response.getEntity()); - String mimeType = ct != null ? ct.getMimeType() : null; - + String mimeType; + if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatusLine().getStatusCode()) { + mimeType = null; + } else { + ContentType ct = ContentType.get(response.getEntity()); + mimeType = ct != null ? ct.getMimeType() : null; + } + Map > headers = new HashMap >(); if (response.getAllHeaders() != null) { for (Header next : response.getAllHeaders()) { @@ -398,7 +403,9 @@ public abstract class BaseClient { charset = ct.getCharset(); } if (charset == null) { - ourLog.warn("Response did not specify a charset."); + if (Constants.STATUS_HTTP_204_NO_CONTENT != theResponse.getStatusLine().getStatusCode()) { + ourLog.warn("Response did not specify a charset."); + } charset = Charset.forName("UTF-8"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java index f28fdeaba2d..0cd4a316e8e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java @@ -59,6 +59,9 @@ public abstract class BaseHttpClientInvocation { public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map > theExtraParams, EncodingEnum theEncoding); protected static void appendExtraParamsWithQuestionMark(Map > theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) { + if (theExtraParams == null) { + return; + } boolean first = theWithQuestionMark; if (theExtraParams != null && theExtraParams.isEmpty() == false) { 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 6ffbd24f3a7..4306964d546 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 @@ -56,10 +56,14 @@ import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.ICreate; import ca.uhn.fhir.rest.gclient.ICreateTyped; +import ca.uhn.fhir.rest.gclient.ICreateWithQuery; +import ca.uhn.fhir.rest.gclient.ICreateWithQueryTyped; import ca.uhn.fhir.rest.gclient.ICriterion; import ca.uhn.fhir.rest.gclient.ICriterionInternal; import ca.uhn.fhir.rest.gclient.IDelete; import ca.uhn.fhir.rest.gclient.IDeleteTyped; +import ca.uhn.fhir.rest.gclient.IDeleteWithQuery; +import ca.uhn.fhir.rest.gclient.IDeleteWithQueryTyped; import ca.uhn.fhir.rest.gclient.IGetPage; import ca.uhn.fhir.rest.gclient.IGetPageTyped; import ca.uhn.fhir.rest.gclient.IGetTags; @@ -76,6 +80,8 @@ import ca.uhn.fhir.rest.gclient.IUntypedQuery; import ca.uhn.fhir.rest.gclient.IUpdate; import ca.uhn.fhir.rest.gclient.IUpdateExecutable; import ca.uhn.fhir.rest.gclient.IUpdateTyped; +import ca.uhn.fhir.rest.gclient.IUpdateWithQuery; +import ca.uhn.fhir.rest.gclient.IUpdateWithQueryTyped; import ca.uhn.fhir.rest.method.DeleteMethodBinding; import ca.uhn.fhir.rest.method.HistoryMethodBinding; import ca.uhn.fhir.rest.method.HttpDeleteClientInvocation; @@ -237,10 +243,10 @@ public class GenericClient extends BaseClient implements IGenericClient { return new LoadPageInternal(); } -// @Override -// public T read(final Class theType, IdDt theId) { -// return doReadOrVRead(theType, theId, false, null, null); -// } + // @Override + // public T read(final Class theType, IdDt theId) { + // return doReadOrVRead(theType, theId, false, null, null); + // } @Override public T read(Class theType, String theId) { @@ -249,7 +255,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); + IdDt id = theUrl instanceof IdDt ? ((IdDt) theUrl) : new IdDt(theUrl); return doReadOrVRead(theType, id, false, null, null); } @@ -447,7 +453,7 @@ public class GenericClient extends BaseClient implements IGenericClient { if (theIfVersionMatches != null) { invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); } - + ResourceResponseHandler binding = new ResourceResponseHandler (theType, id); if (theNotModifiedHandler == null) { @@ -475,18 +481,18 @@ public class GenericClient extends BaseClient implements IGenericClient { return vread(theType, resId); } + private static void addParam(Map > params, String parameterName, String parameterValue) { + if (!params.containsKey(parameterName)) { + params.put(parameterName, new ArrayList ()); + } + params.get(parameterName).add(parameterValue); + } + private abstract class BaseClientExecutable , Y> implements IClientExecutable { private EncodingEnum myParamEncoding; private Boolean myPrettyPrint; private boolean myQueryLogRequestAndResponse; - protected void addParam(Map > params, String parameterName, String parameterValue) { - if (!params.containsKey(parameterName)) { - params.put(parameterName, new ArrayList ()); - } - params.get(parameterName).add(parameterValue); - } - @SuppressWarnings("unchecked") @Override public T andLogRequestAndResponse(boolean theLogRequestAndResponse) { @@ -575,11 +581,13 @@ public class GenericClient extends BaseClient implements IGenericClient { } } - private class CreateInternal extends BaseClientExecutable implements ICreate, ICreateTyped { + private class CreateInternal extends BaseClientExecutable implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped { private String myId; private IResource myResource; private String myResourceBody; + private String mySearchUrl; + private CriterionList myCriterionList; @Override public MethodOutcome execute() { @@ -593,7 +601,14 @@ public class GenericClient extends BaseClient implements IGenericClient { myResourceBody = null; } - BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); + BaseHttpClientInvocation invocation; + if (mySearchUrl != null) { + invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext, mySearchUrl); + } else if (myCriterionList != null) { + invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext, myCriterionList.toParamList()); + } else { + invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); + } RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); @@ -631,15 +646,50 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public ICreateTyped conditionalByUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } + + @Override + public ICreateWithQuery conditional() { + myCriterionList = new CriterionList(); + return this; + } + + @Override + public ICreateWithQueryTyped where(ICriterion> theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public ICreateWithQueryTyped and(ICriterion> theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + } - private class DeleteInternal extends BaseClientExecutable implements IDelete, IDeleteTyped { + private class DeleteInternal extends BaseClientExecutable implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { private IdDt myId; + private String mySearchUrl; + private String myResourceType; + private CriterionList myCriterionList; @Override public BaseOperationOutcome execute() { - HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(myId); + HttpDeleteClientInvocation invocation; + if (myId != null) { + invocation = DeleteMethodBinding.createDeleteInvocation(myId); + } else if (myCriterionList != null) { + Map > params = myCriterionList.toParamList(); + invocation = DeleteMethodBinding.createDeleteInvocation(myResourceType, params); + } else { + invocation = DeleteMethodBinding.createDeleteInvocation(mySearchUrl); + } OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler(); Map > params = new HashMap >(); return invoke(params, binding, invocation); @@ -680,6 +730,56 @@ public class GenericClient extends BaseClient implements IGenericClient { myId = new IdDt(theResourceType, theLogicalId); return this; } + + @Override + public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) { + Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null"); + mySearchUrl = theSearchUrl; + return this; + } + + @Override + public IDeleteWithQuery resourceConditionalByType(String theResourceType) { + Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); + if (myContext.getResourceDefinition(theResourceType) == null) { + throw new IllegalArgumentException("Unknown resource type: " + theResourceType); + } + myResourceType = theResourceType; + myCriterionList = new CriterionList(); + return this; + } + + @Override + public IDeleteWithQueryTyped where(ICriterion> theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public IDeleteWithQueryTyped and(ICriterion> theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + } + + private static class CriterionList extends ArrayList { + + private static final long serialVersionUID = 1L; + + public void populateParamList(Map > theParams) { + for (ICriterionInternal next : this) { + String parameterName = next.getParameterName(); + String parameterValue = next.getParameterValue(); + addParam(theParams, parameterName, parameterValue); + } + } + + public Map > toParamList() { + LinkedHashMap > retVal = new LinkedHashMap >(); + populateParamList(retVal); + return retVal; + } + } private class GetPageInternal extends BaseClientExecutable implements IGetPageTyped { @@ -996,7 +1096,7 @@ public class GenericClient extends BaseClient implements IGenericClient { private class SearchInternal extends BaseClientExecutable implements IQuery, IUntypedQuery { private String myCompartmentName; - private List myCriterion = new ArrayList (); + private CriterionList myCriterion = new CriterionList(); private List myInclude = new ArrayList (); private Integer myParamLimit; private String myResourceId; @@ -1025,11 +1125,7 @@ public class GenericClient extends BaseClient implements IGenericClient { // params.putAll(initial); // } - for (ICriterionInternal next : myCriterion) { - String parameterName = next.getParameterName(); - String parameterValue = next.getParameterValue(); - addParam(params, parameterName, parameterValue); - } + myCriterion.populateParamList(params); for (Include next : myInclude) { addParam(params, Constants.PARAM_INCLUDE, next.getValue()); @@ -1224,30 +1320,39 @@ public class GenericClient extends BaseClient implements IGenericClient { } - private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable { + private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { private IdDt myId; private IResource myResource; private String myResourceBody; + private String mySearchUrl; + private CriterionList myCriterionList; @Override public MethodOutcome execute() { if (myResource == null) { myResource = parseResourceBody(myResourceBody); } - if (myId == null) { - myId = myResource.getId(); - } - if (myId == null || myId.hasIdPart() == false) { - throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); - } // If an explicit encoding is chosen, we will re-serialize to ensure the right encoding if (getParamEncoding() != null) { myResourceBody = null; } - BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); + BaseHttpClientInvocation invocation; + if (mySearchUrl != null) { + invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, mySearchUrl); + } else if (myCriterionList != null) { + invocation = MethodUtil.createUpdateInvocation(myContext, myResource, myResourceBody, myCriterionList.toParamList()); + } else { + if (myId == null) { + myId = myResource.getId(); + } + if (myId == null || myId.hasIdPart() == false) { + throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); + } + invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); + } RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); @@ -1297,6 +1402,30 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IUpdateTyped conditionalByUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } + + @Override + public IUpdateWithQuery conditional() { + myCriterionList = new CriterionList(); + return this; + } + + @Override + public IUpdateWithQueryTyped where(ICriterion> theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public IUpdateWithQueryTyped and(ICriterion> theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java new file mode 100644 index 00000000000..b4ba4c80551 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseQuery.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + +public interface IBaseQuery { + + T where(ICriterion> theCriterion); + + T and(ICriterion> theCriterion); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java index 59516fffe3f..85e09f648f4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java @@ -37,4 +37,19 @@ public interface ICreateTyped extends IClientExecutable [ResourceType]?[Parameters], + * for example: Patient?name=Smith&identifier=13.2.4.11.4%7C847366
+ * @since HAPI 0.9 / FHIR DSTU 2 + */ + ICreateTyped conditionalByUrl(String theSearchUrl); + + /** + * @since HAPI 0.9 / FHIR DSTU 2 + */ + ICreateWithQuery conditional(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java new file mode 100644 index 00000000000..71a98f00c1c --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQuery.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + + +public interface ICreateWithQuery extends IBaseQuery{ + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java new file mode 100644 index 00000000000..927c5ab8523 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateWithQueryTyped.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + +public interface ICreateWithQueryTyped extends ICreateTyped, ICreateWithQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java index 6c6106d49b2..ccd30029fa4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDelete.java @@ -30,5 +30,20 @@ public interface IDelete { IDeleteTyped resourceById(IdDt theId); IDeleteTyped resourceById(String theResourceType, String theLogicalId); - + + /** + * Specifies that the delete should be performed as a conditional delete + * against a given search URL. + * + * @param theSearchUrl The search URL to use. The format of this URL should be of the form [ResourceType]?[Parameters]
, + * for example:Patient?name=Smith&identifier=13.2.4.11.4%7C847366
+ * @since HAPI 0.9 / FHIR DSTU 2 + */ + IDeleteTyped resourceConditionalByUrl(String theSearchUrl); + + /** + * @since HAPI 0.9 / FHIR DSTU 2 + */ + IDeleteWithQuery resourceConditionalByType(String theResourceType); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java new file mode 100644 index 00000000000..6b570fd437b --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQuery.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + +public interface IDeleteWithQuery extends IBaseQuery{ + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java new file mode 100644 index 00000000000..cb7f79e9e60 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IDeleteWithQueryTyped.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + +public interface IDeleteWithQueryTyped extends IDeleteTyped, IDeleteWithQuery { + +} 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 520befc4e3d..20ab12f4404 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 @@ -24,11 +24,7 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.method.SearchStyleEnum; -public interface IQuery extends IClientExecutable { - - IQuery where(ICriterion> theCriterion); - - IQuery and(ICriterion> theCriterion); +public interface IQuery extends IClientExecutable , IBaseQuery { IQuery include(Include theIncludeManagingorganization); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java index 0cd0693c990..d8874fd1db1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateTyped.java @@ -28,4 +28,19 @@ public interface IUpdateTyped extends IUpdateExecutable { IUpdateExecutable withId(String theId); + /** + * Specifies that the update should be performed as a conditional create + * against a given search URL. + * + * @param theSearchUrl The search URL to use. The format of this URL should be of the form [ResourceType]?[Parameters]
, + * for example:Patient?name=Smith&identifier=13.2.4.11.4%7C847366
+ * @since HAPI 0.9 / FHIR DSTU 2 + */ + IUpdateTyped conditionalByUrl(String theSearchUrl); + + /** + * @since HAPI 0.9 / FHIR DSTU 2 + */ + IUpdateWithQuery conditional(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java new file mode 100644 index 00000000000..89799beb1b2 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQuery.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + + +public interface IUpdateWithQuery extends IBaseQuery{ + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java new file mode 100644 index 00000000000..a292405559b --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IUpdateWithQueryTyped.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.rest.gclient; + +/* + * #%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% + */ + +public interface IUpdateWithQueryTyped extends IUpdateTyped, IUpdateWithQuery { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 1b3c0de5393..2fd3533802b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -143,29 +143,55 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = null; } + public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, Map > theParams, String... theUrlPath) { + myContext = theContext; + myResource = null; + myTagList = null; + myUrlPath = StringUtils.join(theUrlPath, '/'); + myResources = null; + myBundle = null; + myContents = theContents; + myContentsIsBundle = false; + myParams = theParams; + myBundleType = null; + } + + public BaseHttpClientInvocationWithContents(FhirContext theContext, IResource theResource, Map > theParams, String... theUrlPath) { + myContext = theContext; + myResource = theResource; + myTagList = null; + myUrlPath = StringUtils.join(theUrlPath, '/'); + myResources = null; + myBundle = null; + myContents = null; + myContentsIsBundle = false; + myParams = theParams; + myBundleType = null; + } + @Override public HttpRequestBase asHttpRequest(String theUrlBase, Map > theExtraParams, EncodingEnum theEncoding) throws DataFormatException { - StringBuilder b = new StringBuilder(); + StringBuilder url = new StringBuilder(); if (myUrlPath == null) { - b.append(theUrlBase); + url.append(theUrlBase); } else { if (!myUrlPath.contains("://")) { - b.append(theUrlBase); + url.append(theUrlBase); if (!theUrlBase.endsWith("/")) { - b.append('/'); + url.append('/'); } } - b.append(myUrlPath); + url.append(myUrlPath); } - appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1); - String url = b.toString(); + appendExtraParamsWithQuestionMark(theExtraParams, url, url.indexOf("?") == -1); if (myResource != null && BaseBinary.class.isAssignableFrom(myResource.getClass())) { BaseBinary binary = (BaseBinary) myResource; ByteArrayEntity entity = new ByteArrayEntity(binary.getContent(), ContentType.parse(binary.getContentType())); HttpRequestBase retVal = createRequest(url, entity); + addMatchHeaders(retVal, url); return retVal; } @@ -177,7 +203,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca if (myContents != null) { encoding = MethodUtil.detectEncoding(myContents); } - + if (encoding == EncodingEnum.JSON) { parser = myContext.newJsonParser(); } else { @@ -187,12 +213,12 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca AbstractHttpEntity entity; if (myParams != null) { - contentType= null; + contentType = null; List parameters = new ArrayList (); for (Entry > nextParam : myParams.entrySet()) { List value = nextParam.getValue(); - for(String s: value){ - parameters.add(new BasicNameValuePair(nextParam.getKey(), s)); + for (String s : value) { + parameters.add(new BasicNameValuePair(nextParam.getKey(), s)); } } try { @@ -228,6 +254,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca HttpRequestBase retVal = createRequest(url, entity); super.addHeadersToRequest(retVal); + addMatchHeaders(retVal, url); if (contentType != null) { retVal.addHeader(Constants.HEADER_CONTENT_TYPE, contentType + Constants.HEADER_SUFFIX_CT_UTF_8); @@ -236,6 +263,41 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca return retVal; } - protected abstract HttpRequestBase createRequest(String url, AbstractHttpEntity theEntity); + private void addMatchHeaders(HttpRequestBase theHttpRequest, StringBuilder theUrlBase) { + if (myIfNoneExistParams != null) { + StringBuilder b = newHeaderBuilder(theUrlBase); + appendExtraParamsWithQuestionMark(myIfNoneExistParams, b, b.indexOf("?") == -1); + theHttpRequest.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + + if (myIfNoneExistString != null) { + StringBuilder b = newHeaderBuilder(theUrlBase); + b.append(b.indexOf("?") == -1 ? '?' : '&'); + b.append(myIfNoneExistString.substring(myIfNoneExistString.indexOf('?') + 1)); + theHttpRequest.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + } + + private StringBuilder newHeaderBuilder(StringBuilder theUrlBase) { + StringBuilder b = new StringBuilder(); + b.append(theUrlBase); + if (theUrlBase.length() > 0 && theUrlBase.charAt(theUrlBase.length() - 1) == '/') { + b.deleteCharAt(b.length() - 1); + } + return b; + } + + protected abstract HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity); + + private Map > myIfNoneExistParams; + private String myIfNoneExistString; + + public void setIfNoneExistParams(Map > theIfNoneExist) { + myIfNoneExistParams = theIfNoneExist; + } + + public void setIfNoneExistString(String theIfNoneExistString) { + myIfNoneExistString = theIfNoneExistString; + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index d595ae49665..27868f5d46d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -26,15 +26,12 @@ import java.io.IOException; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.TreeSet; -import ca.uhn.fhir.rest.annotation.*; - import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.IBaseResource; @@ -47,6 +44,18 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.annotation.AddTags; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.DeleteTags; +import ca.uhn.fhir.rest.annotation.GetTags; +import ca.uhn.fhir.rest.annotation.History; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; @@ -61,9 +70,9 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; -import ca.uhn.fhir.rest.server.exceptions.ResourceVersionNotSpecifiedException; import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.ReflectionUtil; @@ -217,7 +226,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler ex = new ResourceVersionConflictException("Server responded with HTTP 409"); break; case Constants.STATUS_HTTP_412_PRECONDITION_FAILED: - ex = new ResourceVersionNotSpecifiedException("Server responded with HTTP 412"); + ex = new PreconditionFailedException("Server responded with HTTP 412"); break; case Constants.STATUS_HTTP_422_UNPROCESSABLE_ENTITY: IParser parser = createAppropriateParserForParsingResponse(theResponseMimeType, theResponseReader, theStatusCode); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java index 3dafdde18fe..f1c5da22ade 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java @@ -273,11 +273,16 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding wantedResourceType = requestContainsResourceType(); + IResource retVal; if (wantedResourceType != null) { - return (IResource) parser.parseResource(wantedResourceType, requestReader); + retVal = (IResource) parser.parseResource(wantedResourceType, requestReader); } else { - return parser.parseResource(requestReader); + retVal = parser.parseResource(requestReader); } + + retVal.setId(theRequest.getId()); + + return retVal; } protected abstract Set provideAllowableRequestTypes(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java new file mode 100644 index 00000000000..c5bbdd43964 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java @@ -0,0 +1,57 @@ +package ca.uhn.fhir.rest.method; + +/* + * #%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.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +class ConditionalParamBinder implements IParameter { + + ConditionalParamBinder() { + super(); + } + + @Override + public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map > theTargetQueryArguments) throws InternalErrorException { + throw new UnsupportedOperationException(); + } + + @Override + public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { + if (theRequest.getId() != null && theRequest.getId().hasIdPart()) { + return null; + } + int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?'); + return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex); + } + + @Override + public void initializeTypes(Method theMethod, Class extends Collection>> theOuterCollectionType, Class extends Collection>> theInnerCollectionType, Class> theParameterType) { + // nothing + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java index 4a01f4c87e1..ee07daf5205 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java @@ -22,6 +22,8 @@ package ca.uhn.fhir.rest.method; import java.lang.reflect.Method; import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Set; import ca.uhn.fhir.context.ConfigurationException; @@ -154,5 +156,14 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { return null; } + public static HttpDeleteClientInvocation createDeleteInvocation(String theSearchUrl) { + HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theSearchUrl); + return retVal; + } + + public static HttpDeleteClientInvocation createDeleteInvocation(String theResourceType, Map > theParams) { + return new HttpDeleteClientInvocation(theResourceType, theParams); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java index cc14fc917a0..3bee2d4770e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java @@ -33,12 +33,22 @@ import ca.uhn.fhir.rest.server.EncodingEnum; public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { private String myUrlPath; + private Map > myParams; public HttpDeleteClientInvocation(IdDt theId) { super(); myUrlPath = theId.toUnqualifiedVersionless().getValue(); } + public HttpDeleteClientInvocation(String theSearchUrl) { + myUrlPath = theSearchUrl; + } + + public HttpDeleteClientInvocation(String theResourceType, Map > theParams) { + myUrlPath = theResourceType; + myParams = theParams; + } + @Override public HttpRequestBase asHttpRequest(String theUrlBase, Map > theExtraParams, EncodingEnum theEncoding) { StringBuilder b = new StringBuilder(); @@ -48,12 +58,12 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { } b.append(myUrlPath); - appendExtraParamsWithQuestionMark(theExtraParams, b,b.indexOf("?") == -1); + appendExtraParamsWithQuestionMark(myParams, b, b.indexOf("?") == -1); + appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1); HttpDelete retVal = new HttpDelete(b.toString()); super.addHeadersToRequest(retVal); return retVal; } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java index 97a8a152eaf..8dcb990f43e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java @@ -34,38 +34,34 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum; public class HttpPostClientInvocation extends BaseHttpClientInvocationWithContents { + public HttpPostClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) { super(theContext, theResource, theUrlExtension); } - public HttpPostClientInvocation(FhirContext theContext, TagList theTagList, String... theUrlExtension) { super(theContext, theTagList, theUrlExtension); } - public HttpPostClientInvocation(FhirContext theContext, List theResources, BundleTypeEnum theBundleType) { super(theContext, theResources, theBundleType); } - public HttpPostClientInvocation(FhirContext theContext, Bundle theBundle) { - super(theContext,theBundle); + super(theContext, theBundle); } public HttpPostClientInvocation(FhirContext theContext, String theContents, boolean theIsBundle, String theUrlExtension) { - super(theContext,theContents, theIsBundle, theUrlExtension); + super(theContext, theContents, theIsBundle, theUrlExtension); } - public HttpPostClientInvocation(FhirContext theContext, Map > theParams, String... theUrlExtension) { super(theContext, theParams, theUrlExtension); } - @Override - protected HttpPost createRequest(String url, AbstractHttpEntity theEntity) { - HttpPost retVal = new HttpPost(url); + protected HttpPost createRequest(StringBuilder theUrlBase, AbstractHttpEntity theEntity) { + HttpPost retVal = new HttpPost(theUrlBase.toString()); retVal.setEntity(theEntity); return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java index 68822ca5c0b..e9f351c1100 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java @@ -34,15 +34,14 @@ public class HttpPutClientInvocation extends BaseHttpClientInvocationWithContent } public HttpPutClientInvocation(FhirContext theContext, String theContents, boolean theIsBundle, String theUrlExtension) { - super(theContext,theContents, theIsBundle, theUrlExtension); + super(theContext, theContents, theIsBundle, theUrlExtension); } @Override - protected HttpRequestBase createRequest(String url, AbstractHttpEntity theEntity) { - HttpPut retVal = new HttpPut(url); + protected HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity) { + HttpPut retVal = new HttpPut(theUrl.toString()); retVal.setEntity(theEntity); return retVal; } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index 5b5193b20d9..accc6f3ec55 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -5,14 +5,17 @@ import static org.apache.commons.lang3.StringUtils.*; import java.io.IOException; import java.io.PushbackReader; import java.io.Reader; +import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -44,6 +47,7 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.annotation.ConditionalOperationParam; import ca.uhn.fhir.rest.annotation.Count; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; @@ -95,9 +99,65 @@ import ca.uhn.fhir.util.ReflectionUtil; public class MethodUtil { private static final String LABEL = "label=\""; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); + private static final String SCHEME = "scheme=\""; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MethodUtil.class); + static void addTagsToPostOrPut(IResource resource, BaseHttpClientInvocation retVal) { + TagList list = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); + if (list != null) { + for (Tag tag : list) { + if (StringUtils.isNotBlank(tag.getTerm())) { + retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); + } + } + } + } + + public static HttpGetClientInvocation createConformanceInvocation() { + return new HttpGetClientInvocation("metadata"); + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, FhirContext theContext) { + return createCreateInvocation(theResource, null, null, theContext); + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext) { + RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); + String resourceName = def.getName(); + + StringBuilder urlExtension = new StringBuilder(); + urlExtension.append(resourceName); + + if (StringUtils.isNotBlank(theId)) { + urlExtension.append('/'); + urlExtension.append(theId); + } + + HttpPostClientInvocation retVal; + if (StringUtils.isBlank(theResourceBody)) { + retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString()); + } else { + retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString()); + } + addTagsToPostOrPut(theResource, retVal); + + // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); + + return retVal; + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext, Map > theIfNoneExistParams) { + HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); + retVal.setIfNoneExistParams(theIfNoneExistParams); + return retVal; + } + + public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext, String theIfNoneExistUrl) { + HttpPostClientInvocation retVal = createCreateInvocation(theResource, theResourceBody, theId, theContext); + retVal.setIfNoneExistString(theIfNoneExistUrl); + return retVal; + } public static HttpPutClientInvocation createUpdateInvocation(IResource theResource, String theResourceBody, IdDt theId, FhirContext theContext) { String resourceName = theContext.getResourceDefinition(theResource).getName(); @@ -105,9 +165,9 @@ public class MethodUtil { urlBuilder.append(resourceName); urlBuilder.append('/'); urlBuilder.append(theId.getIdPart()); + String urlExtension = urlBuilder.toString(); HttpPutClientInvocation retVal; - String urlExtension = urlBuilder.toString(); if (StringUtils.isBlank(theResourceBody)) { retVal = new HttpPutClientInvocation(theContext, theResource, urlExtension); } else { @@ -135,203 +195,49 @@ public class MethodUtil { return retVal; } - public static void parseClientRequestResourceHeaders(IdDt theRequestedId, Map > theHeaders, IBaseResource resource) { - List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); - if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { - String headerValue = lmHeaders.get(0); - Date headerDateValue; - try { - headerDateValue = DateUtils.parseDate(headerValue); - if (resource instanceof IResource) { - InstantDt lmValue = new InstantDt(headerDateValue); - ((IResource) resource).getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); - } else if (resource instanceof IAnyResource) { - ((IAnyResource) resource).getMeta().setLastUpdated(headerDateValue); - } - } catch (Exception e) { - ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString()); - } - } + public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IResource theResource, String theResourceBody, Map > theMatchParams) { + StringBuilder b = new StringBuilder(); - List clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); - if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) { - String headerValue = clHeaders.get(0); - if (isNotBlank(headerValue)) { - new IdDt(headerValue).applyTo(resource); - } - } + String resourceType = theContext.getResourceDefinition(theResource).getName(); + b.append(resourceType); - IdDt existing = IdDt.of(resource); - - List eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC); - String eTagVersion = null; - if (eTagHeaders != null && eTagHeaders.size() > 0) { - eTagVersion = parseETagValue(eTagHeaders.get(0)); - } - if (isNotBlank(eTagVersion)) { - if (existing == null || existing.isEmpty()) { - if (theRequestedId != null) { - theRequestedId.withVersion(eTagVersion).applyTo(resource); - } - } else if (existing.hasVersionIdPart() == false) { - existing.withVersion(eTagVersion).applyTo(resource); - } - } else if (existing == null || existing.isEmpty()) { - if (theRequestedId != null) { - theRequestedId.applyTo(resource); - } - } - - List categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC); - if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) { - TagList tagList = new TagList(); - for (String header : categoryHeaders) { - parseTagValue(tagList, header); - } - if (resource instanceof IResource) { - ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList); - } else if (resource instanceof IAnyResource) { - IMetaType meta = ((IAnyResource) resource).getMeta(); - for (Tag next : tagList) { - meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel()); + boolean haveQuestionMark = false; + for (Entry > nextEntry : theMatchParams.entrySet()) { + for (String nextValue : nextEntry.getValue()) { + b.append(haveQuestionMark ? '&' : '?'); + haveQuestionMark = true; + try { + b.append(URLEncoder.encode(nextEntry.getKey(), "UTF-8")); + b.append('='); + b.append(URLEncoder.encode(nextValue, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new ConfigurationException("UTF-8 not supported on this platform"); } } } - } - public static String parseETagValue(String value) { - String eTagVersion; - value = value.trim(); - if (value.length() > 1) { - if (value.charAt(value.length() - 1) == '"') { - if (value.charAt(0) == '"') { - eTagVersion = value.substring(1, value.length() - 1); - } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') { - eTagVersion = value.substring(3, value.length() - 1); - } else { - eTagVersion = value; - } - } else { - eTagVersion = value; - } - } else { - eTagVersion = value; - } - return eTagVersion; - } - public static void parseTagValue(TagList tagList, String nextTagComplete) { - StringBuilder next = new StringBuilder(nextTagComplete); - parseTagValue(tagList, nextTagComplete, next); - } - - private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) { - int firstSemicolon = theBuffer.indexOf(";"); - int deleteTo; - if (firstSemicolon == -1) { - firstSemicolon = theBuffer.indexOf(","); - if (firstSemicolon == -1) { - firstSemicolon = theBuffer.length(); - deleteTo = theBuffer.length(); - } else { - deleteTo = firstSemicolon; - } - } else { - deleteTo = firstSemicolon + 1; - } - - String term = theBuffer.substring(0, firstSemicolon); - String scheme = null; - String label = null; - if (isBlank(term)) { - return; - } - - theBuffer.delete(0, deleteTo); - while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { - theBuffer.deleteCharAt(0); - } - - while (theBuffer.length() > 0) { - boolean foundSomething = false; - if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) { - int closeIdx = theBuffer.indexOf("\"", SCHEME.length()); - scheme = theBuffer.substring(SCHEME.length(), closeIdx); - theBuffer.delete(0, closeIdx + 1); - foundSomething = true; - } - if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) { - int closeIdx = theBuffer.indexOf("\"", LABEL.length()); - label = theBuffer.substring(LABEL.length(), closeIdx); - theBuffer.delete(0, closeIdx + 1); - foundSomething = true; - } - // TODO: support enc2231-string as described in - // http://tools.ietf.org/html/draft-johnston-http-category-header-02 - // TODO: support multiple tags in one header as described in - // http://hl7.org/implement/standards/fhir/http.html#tags - - while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) { - theBuffer.deleteCharAt(0); - } - - if (!foundSomething) { - break; - } - } - - if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') { - theBuffer.deleteCharAt(0); - while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { - theBuffer.deleteCharAt(0); - } - theTagList.add(new Tag(scheme, term, label)); - parseTagValue(theTagList, theCompleteHeaderValue, theBuffer); - } else { - theTagList.add(new Tag(scheme, term, label)); - } - - if (theBuffer.length() > 0) { - ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue); - } - - } - - static void addTagsToPostOrPut(IResource resource, BaseHttpClientInvocation retVal) { - TagList list = (TagList) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); - if (list != null) { - for (Tag tag : list) { - if (StringUtils.isNotBlank(tag.getTerm())) { - retVal.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); - } - } - } - } - - public static HttpPostClientInvocation createCreateInvocation(IResource theResource, FhirContext theContext) { - return createCreateInvocation(theResource, null, null, theContext); - } - - public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext) { - RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource); - String resourceName = def.getName(); - - StringBuilder urlExtension = new StringBuilder(); - urlExtension.append(resourceName); - if (StringUtils.isNotBlank(theId)) { - urlExtension.append('/'); - urlExtension.append(theId); - } - - HttpPostClientInvocation retVal; + HttpPutClientInvocation retVal; if (StringUtils.isBlank(theResourceBody)) { - retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString()); + retVal = new HttpPutClientInvocation(theContext, theResource, b.toString()); } else { - retVal = new HttpPostClientInvocation(theContext, theResourceBody, false, urlExtension.toString()); + retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, b.toString()); } + addTagsToPostOrPut(theResource, retVal); - // addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody); + return retVal; + } + + public static HttpPutClientInvocation createUpdateInvocation(FhirContext theContext, IResource theResource, String theResourceBody, String theMatchUrl) { + HttpPutClientInvocation retVal; + if (StringUtils.isBlank(theResourceBody)) { + retVal = new HttpPutClientInvocation(theContext, theResource, theMatchUrl); + } else { + retVal = new HttpPutClientInvocation(theContext, theResourceBody, false, theMatchUrl); + } + + addTagsToPostOrPut(theResource, retVal); return retVal; } @@ -339,66 +245,30 @@ public class MethodUtil { public static EncodingEnum detectEncoding(String theBody) { for (int i = 0; i < theBody.length(); i++) { switch (theBody.charAt(i)) { - case '<': - return EncodingEnum.XML; - case '{': - return EncodingEnum.JSON; + case '<': + return EncodingEnum.XML; + case '{': + return EncodingEnum.JSON; } } return EncodingEnum.XML; } - public static HttpGetClientInvocation createConformanceInvocation() { - return new HttpGetClientInvocation("metadata"); - } - - public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map > theHeaders) { - List locationHeaders = new ArrayList (); - List lh = theHeaders.get(Constants.HEADER_LOCATION_LC); - if (lh != null) { - locationHeaders.addAll(lh); - } - List clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); - if (clh != null) { - locationHeaders.addAll(clh); - } - - MethodOutcome retVal = new MethodOutcome(); - if (locationHeaders != null && locationHeaders.size() > 0) { - String locationHeader = locationHeaders.get(0); - BaseOutcomeReturningMethodBinding.parseContentLocation(retVal, theResourceName, locationHeader); - } - if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { - EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); - if (ct != null) { - PushbackReader reader = new PushbackReader(theResponseReader); - - try { - int firstByte = reader.read(); - if (firstByte == -1) { - BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read"); - reader = null; - } else { - reader.unread(firstByte); - } - } catch (IOException e) { - BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e); - reader = null; + public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) { + for (Annotation annotation : theAnnotations) { + if (annotation instanceof Description) { + Description desc = (Description) annotation; + if (isNotBlank(desc.formalDefinition())) { + theParameter.setDescription(desc.formalDefinition()); + } else { + theParameter.setDescription(desc.shortDefinition()); } - - if (reader != null) { - IParser parser = ct.newParser(theContext); - IResource outcome = parser.parseResource(reader); - if (outcome instanceof BaseOperationOutcome) { - retVal.setOperationOutcome((BaseOperationOutcome) outcome); - } - } - - } else { - BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType); } } - return retVal; + } + + public static Integer findIdParameterIndex(Method theMethod) { + return MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class); } public static Integer findParamAnnotationIndex(Method theMethod, Class> toFind) { @@ -416,38 +286,12 @@ public class MethodUtil { return null; } - public static void extractDescription(SearchParameter theParameter, Annotation[] theAnnotations) { - for (Annotation annotation : theAnnotations) { - if (annotation instanceof Description) { - Description desc = (Description) annotation; - if (isNotBlank(desc.formalDefinition())) { - theParameter.setDescription(desc.formalDefinition()); - } else { - theParameter.setDescription(desc.shortDefinition()); - } - } - } + public static Integer findTagListParameterIndex(Method theMethod) { + return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class); } - public static IQueryParameterOr> singleton(final IQueryParameterType theParam) { - return new IQueryParameterOr () { - - @Override - public void setValuesAsQueryTokens(QualifiedParamList theParameters) { - if (theParameters.isEmpty()) { - return; - } - if (theParameters.size() > 1) { - throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); - } - theParam.setValueAsQueryToken(theParameters.getQualifier(), theParameters.get(0)); - } - - @Override - public List getValuesAsQueryTokens() { - return Collections.singletonList(theParam); - } - }; + public static Integer findConditionalOperationParameterIndex(Method theMethod) { + return MethodUtil.findParamAnnotationIndex(theMethod, ConditionalOperationParam.class); } @SuppressWarnings("deprecation") @@ -455,46 +299,6 @@ public class MethodUtil { return MethodUtil.findParamAnnotationIndex(theMethod, VersionIdParam.class); } - public static Integer findIdParameterIndex(Method theMethod) { - return MethodUtil.findParamAnnotationIndex(theMethod, IdParam.class); - } - - public static Integer findTagListParameterIndex(Method theMethod) { - return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class); - } - - /** - * This is a utility method intended provided to help the JPA module. - */ - public static IQueryParameterAnd> parseQueryParams(RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List theParameters) { - QueryParameterAndBinder binder = null; - switch (theParamDef.getParamType()) { - case COMPOSITE: - throw new UnsupportedOperationException(); - case DATE: - binder = new QueryParameterAndBinder(DateAndListParam.class, Collections. >emptyList()); - break; - case NUMBER: - binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections. >emptyList()); - break; - case QUANTITY: - binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections. >emptyList()); - break; - case REFERENCE: - binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections. >emptyList()); - break; - case STRING: - binder = new QueryParameterAndBinder(StringAndListParam.class, Collections. >emptyList()); - break; - case TOKEN: - binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections. >emptyList()); - break; - } - - return binder.parse(theUnqualifiedParamName, theParameters); - } - - @SuppressWarnings("unchecked") public static List getResourceParameters(FhirContext theContext, Method theMethod, Object theProvider) { List parameters = new ArrayList (); @@ -591,6 +395,8 @@ public class MethodUtil { param = new SortParameter(); } else if (nextAnnotation instanceof TransactionParam) { param = new TransactionParamBinder(theContext); + } else if (nextAnnotation instanceof ConditionalOperationParam) { + param = new ConditionalParamBinder(); } else { continue; } @@ -612,4 +418,267 @@ public class MethodUtil { return parameters; } + public static void parseClientRequestResourceHeaders(IdDt theRequestedId, Map > theHeaders, IBaseResource resource) { + List lmHeaders = theHeaders.get(Constants.HEADER_LAST_MODIFIED_LOWERCASE); + if (lmHeaders != null && lmHeaders.size() > 0 && StringUtils.isNotBlank(lmHeaders.get(0))) { + String headerValue = lmHeaders.get(0); + Date headerDateValue; + try { + headerDateValue = DateUtils.parseDate(headerValue); + if (resource instanceof IResource) { + InstantDt lmValue = new InstantDt(headerDateValue); + ((IResource) resource).getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, lmValue); + } else if (resource instanceof IAnyResource) { + ((IAnyResource) resource).getMeta().setLastUpdated(headerDateValue); + } + } catch (Exception e) { + ourLog.warn("Unable to parse date string '{}'. Error is: {}", headerValue, e.toString()); + } + } + + List clHeaders = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); + if (clHeaders != null && clHeaders.size() > 0 && StringUtils.isNotBlank(clHeaders.get(0))) { + String headerValue = clHeaders.get(0); + if (isNotBlank(headerValue)) { + new IdDt(headerValue).applyTo(resource); + } + } + + IdDt existing = IdDt.of(resource); + + List eTagHeaders = theHeaders.get(Constants.HEADER_ETAG_LC); + String eTagVersion = null; + if (eTagHeaders != null && eTagHeaders.size() > 0) { + eTagVersion = parseETagValue(eTagHeaders.get(0)); + } + if (isNotBlank(eTagVersion)) { + if (existing == null || existing.isEmpty()) { + if (theRequestedId != null) { + theRequestedId.withVersion(eTagVersion).applyTo(resource); + } + } else if (existing.hasVersionIdPart() == false) { + existing.withVersion(eTagVersion).applyTo(resource); + } + } else if (existing == null || existing.isEmpty()) { + if (theRequestedId != null) { + theRequestedId.applyTo(resource); + } + } + + List categoryHeaders = theHeaders.get(Constants.HEADER_CATEGORY_LC); + if (categoryHeaders != null && categoryHeaders.size() > 0 && StringUtils.isNotBlank(categoryHeaders.get(0))) { + TagList tagList = new TagList(); + for (String header : categoryHeaders) { + parseTagValue(tagList, header); + } + if (resource instanceof IResource) { + ResourceMetadataKeyEnum.TAG_LIST.put((IResource) resource, tagList); + } else if (resource instanceof IAnyResource) { + IMetaType meta = ((IAnyResource) resource).getMeta(); + for (Tag next : tagList) { + meta.addTag().setSystem(next.getScheme()).setCode(next.getTerm()).setDisplay(next.getLabel()); + } + } + } + } + + public static String parseETagValue(String value) { + String eTagVersion; + value = value.trim(); + if (value.length() > 1) { + if (value.charAt(value.length() - 1) == '"') { + if (value.charAt(0) == '"') { + eTagVersion = value.substring(1, value.length() - 1); + } else if (value.length() > 3 && value.charAt(0) == 'W' && value.charAt(1) == '/' && value.charAt(2) == '"') { + eTagVersion = value.substring(3, value.length() - 1); + } else { + eTagVersion = value; + } + } else { + eTagVersion = value; + } + } else { + eTagVersion = value; + } + return eTagVersion; + } + + /** + * This is a utility method intended provided to help the JPA module. + */ + public static IQueryParameterAnd> parseQueryParams(RuntimeSearchParam theParamDef, String theUnqualifiedParamName, List theParameters) { + QueryParameterAndBinder binder = null; + switch (theParamDef.getParamType()) { + case COMPOSITE: + throw new UnsupportedOperationException(); + case DATE: + binder = new QueryParameterAndBinder(DateAndListParam.class, Collections. >emptyList()); + break; + case NUMBER: + binder = new QueryParameterAndBinder(NumberAndListParam.class, Collections. >emptyList()); + break; + case QUANTITY: + binder = new QueryParameterAndBinder(QuantityAndListParam.class, Collections. >emptyList()); + break; + case REFERENCE: + binder = new QueryParameterAndBinder(ReferenceAndListParam.class, Collections. >emptyList()); + break; + case STRING: + binder = new QueryParameterAndBinder(StringAndListParam.class, Collections. >emptyList()); + break; + case TOKEN: + binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections. >emptyList()); + break; + } + + return binder.parse(theUnqualifiedParamName, theParameters); + } + + public static void parseTagValue(TagList tagList, String nextTagComplete) { + StringBuilder next = new StringBuilder(nextTagComplete); + parseTagValue(tagList, nextTagComplete, next); + } + + private static void parseTagValue(TagList theTagList, String theCompleteHeaderValue, StringBuilder theBuffer) { + int firstSemicolon = theBuffer.indexOf(";"); + int deleteTo; + if (firstSemicolon == -1) { + firstSemicolon = theBuffer.indexOf(","); + if (firstSemicolon == -1) { + firstSemicolon = theBuffer.length(); + deleteTo = theBuffer.length(); + } else { + deleteTo = firstSemicolon; + } + } else { + deleteTo = firstSemicolon + 1; + } + + String term = theBuffer.substring(0, firstSemicolon); + String scheme = null; + String label = null; + if (isBlank(term)) { + return; + } + + theBuffer.delete(0, deleteTo); + while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { + theBuffer.deleteCharAt(0); + } + + while (theBuffer.length() > 0) { + boolean foundSomething = false; + if (theBuffer.length() > SCHEME.length() && theBuffer.substring(0, SCHEME.length()).equals(SCHEME)) { + int closeIdx = theBuffer.indexOf("\"", SCHEME.length()); + scheme = theBuffer.substring(SCHEME.length(), closeIdx); + theBuffer.delete(0, closeIdx + 1); + foundSomething = true; + } + if (theBuffer.length() > LABEL.length() && theBuffer.substring(0, LABEL.length()).equals(LABEL)) { + int closeIdx = theBuffer.indexOf("\"", LABEL.length()); + label = theBuffer.substring(LABEL.length(), closeIdx); + theBuffer.delete(0, closeIdx + 1); + foundSomething = true; + } + // TODO: support enc2231-string as described in + // http://tools.ietf.org/html/draft-johnston-http-category-header-02 + // TODO: support multiple tags in one header as described in + // http://hl7.org/implement/standards/fhir/http.html#tags + + while (theBuffer.length() > 0 && (theBuffer.charAt(0) == ' ' || theBuffer.charAt(0) == ';')) { + theBuffer.deleteCharAt(0); + } + + if (!foundSomething) { + break; + } + } + + if (theBuffer.length() > 0 && theBuffer.charAt(0) == ',') { + theBuffer.deleteCharAt(0); + while (theBuffer.length() > 0 && theBuffer.charAt(0) == ' ') { + theBuffer.deleteCharAt(0); + } + theTagList.add(new Tag(scheme, term, label)); + parseTagValue(theTagList, theCompleteHeaderValue, theBuffer); + } else { + theTagList.add(new Tag(scheme, term, label)); + } + + if (theBuffer.length() > 0) { + ourLog.warn("Ignoring extra text at the end of " + Constants.HEADER_CATEGORY + " tag '" + theBuffer.toString() + "' - Complete tag value was: " + theCompleteHeaderValue); + } + + } + + public static MethodOutcome process2xxResponse(FhirContext theContext, String theResourceName, int theResponseStatusCode, String theResponseMimeType, Reader theResponseReader, Map > theHeaders) { + List locationHeaders = new ArrayList (); + List lh = theHeaders.get(Constants.HEADER_LOCATION_LC); + if (lh != null) { + locationHeaders.addAll(lh); + } + List clh = theHeaders.get(Constants.HEADER_CONTENT_LOCATION_LC); + if (clh != null) { + locationHeaders.addAll(clh); + } + + MethodOutcome retVal = new MethodOutcome(); + if (locationHeaders != null && locationHeaders.size() > 0) { + String locationHeader = locationHeaders.get(0); + BaseOutcomeReturningMethodBinding.parseContentLocation(retVal, theResourceName, locationHeader); + } + if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) { + EncodingEnum ct = EncodingEnum.forContentType(theResponseMimeType); + if (ct != null) { + PushbackReader reader = new PushbackReader(theResponseReader); + + try { + int firstByte = reader.read(); + if (firstByte == -1) { + BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read"); + reader = null; + } else { + reader.unread(firstByte); + } + } catch (IOException e) { + BaseOutcomeReturningMethodBinding.ourLog.debug("No content in response, not going to read", e); + reader = null; + } + + if (reader != null) { + IParser parser = ct.newParser(theContext); + IResource outcome = parser.parseResource(reader); + if (outcome instanceof BaseOperationOutcome) { + retVal.setOperationOutcome((BaseOperationOutcome) outcome); + } + } + + } else { + BaseOutcomeReturningMethodBinding.ourLog.debug("Ignoring response content of type: {}", theResponseMimeType); + } + } + return retVal; + } + + public static IQueryParameterOr> singleton(final IQueryParameterType theParam) { + return new IQueryParameterOr () { + + @Override + public List getValuesAsQueryTokens() { + return Collections.singletonList(theParam); + } + + @Override + public void setValuesAsQueryTokens(QualifiedParamList theParameters) { + if (theParameters.isEmpty()) { + return; + } + if (theParameters.size() > 1) { + throw new IllegalArgumentException("Type " + theParam.getClass().getCanonicalName() + " does not support multiple values"); + } + theParam.setValueAsQueryToken(theParameters.getQualifier(), theParameters.get(0)); + } + }; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java index 3974cfef2bf..965832c2a11 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java @@ -20,19 +20,17 @@ package ca.uhn.fhir.rest.method; * #L% */ -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.lang.reflect.Method; import java.util.Collections; import java.util.Set; -import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; @@ -43,16 +41,11 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { private Integer myIdParameterIndex; - private Integer myVersionIdParameterIndex; public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { super(theMethod, theContext, Update.class, theProvider); myIdParameterIndex = MethodUtil.findIdParameterIndex(theMethod); - if (myIdParameterIndex == null) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation"); - } - myVersionIdParameterIndex = MethodUtil.findVersionIdParameterIndex(theMethod); } @Override @@ -92,7 +85,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP if (theRequest.getId() != null && theRequest.getId().hasVersionIdPart() == false) { if (id != null && id.hasVersionIdPart()) { - theRequest.setId(id); + theRequest.getId().setValue(id.getValue()); } } @@ -104,9 +97,8 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP } } - theParams[myIdParameterIndex] = theRequest.getId(); - if (myVersionIdParameterIndex != null) { - theParams[myVersionIdParameterIndex] = theRequest.getId(); + if (myIdParameterIndex != null) { + theParams[myIdParameterIndex] = theRequest.getId(); } } @@ -117,12 +109,6 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP throw new NullPointerException("ID can not be null"); } - if (myVersionIdParameterIndex != null) { - IdDt versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex]; - if (idDt.hasVersionIdPart() == false) { - idDt = idDt.withVersion(versionIdDt.getIdPart()); - } - } FhirContext context = getContext(); HttpPutClientInvocation retVal = MethodUtil.createUpdateInvocation(theResource, null, idDt, context); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java index dbd875641e6..a087c927640 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/InternalCodingDt.java @@ -49,8 +49,7 @@ import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.UriDt; -public class InternalCodingDt - extends BaseCodingDt implements ICompositeDatatype +public class InternalCodingDt extends BaseCodingDt implements ICompositeDatatype { /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 477d9a00b0a..d285cb98eb2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -111,6 +111,8 @@ public class Constants { public static final int STATUS_HTTP_501_NOT_IMPLEMENTED = 501; public static final String URL_TOKEN_HISTORY = "_history"; public static final String URL_TOKEN_METADATA = "metadata"; + public static final String HEADER_IF_NONE_EXIST = "If-None-Exist"; + public static final String HEADER_IF_NONE_EXIST_LC = HEADER_IF_NONE_EXIST.toLowerCase(); static { Map valToEncoding = new HashMap (); diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java index 58565ddf287..764bec73488 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.IBaseResource; public interface IAnyResource extends IBaseResource { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBackboneElement.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBackboneElement.java index 15841fa2396..da77cb61bd0 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBackboneElement.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBackboneElement.java @@ -1,5 +1,27 @@ package org.hl7.fhir.instance.model.api; -public interface IBackboneElement { +/* + * #%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 org.hl7.fhir.instance.model.IBase; + +public interface IBackboneElement extends IBase { } diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBooleanDatatype.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBooleanDatatype.java index 4162693da73..9b2066e6764 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBooleanDatatype.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBooleanDatatype.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.IPrimitiveType; public interface IBaseBooleanDatatype extends IPrimitiveType { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBundle.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBundle.java index 7e92f595067..b0d4a79ea9d 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBundle.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseBundle.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.IBaseResource; public interface IBaseBundle extends IBaseResource { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDatatype.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDatatype.java index 24a59aac24a..24a4f525c22 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDatatype.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDatatype.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.IBase; public interface IBaseDatatype extends IBase { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDecimalDatatype.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDecimalDatatype.java index bcb5c96beb9..cb68921fdeb 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDecimalDatatype.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseDecimalDatatype.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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.math.BigDecimal; import org.hl7.fhir.instance.model.IPrimitiveType; diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseElement.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseElement.java index e53c06a2422..95d0797308d 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseElement.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseElement.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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% + */ + public interface IBaseElement { IBaseElement setId(String theValue); diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseEnumFactory.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseEnumFactory.java new file mode 100644 index 00000000000..fbbc9741c37 --- /dev/null +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseEnumFactory.java @@ -0,0 +1,42 @@ +package org.hl7.fhir.instance.model.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% + */ + +public interface IBaseEnumFactory > { + + /** + * Read an enumeration value from the string that represents it on the XML or JSON + * + * @param codeString the value found in the XML or JSON + * @return the enumeration value + * @throws IllegalArgumentException is the value is not known + */ + public T fromCode(String codeString) throws IllegalArgumentException; + + /** + * Get the XML/JSON representation for an enumerated value + * + * @param code - the enumeration value + * @return the XML/JSON representation + */ + public String toCode(T code); + +} diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseExtension.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseExtension.java index 83ad1ce865c..2079feae03f 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseExtension.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseExtension.java @@ -1,7 +1,39 @@ package org.hl7.fhir.instance.model.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.List; + import org.hl7.fhir.instance.model.ICompositeType; -public interface IBaseExtension extends ICompositeType { +public interface IBaseExtension extends ICompositeType { + + List getExtension(); + + String getUrl(); + + IBaseDatatype getValue(); + + T setUrl(String theUrl); + + T setValue(IBaseDatatype theValue); } diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java new file mode 100644 index 00000000000..566f88ebc8f --- /dev/null +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java @@ -0,0 +1,31 @@ +package org.hl7.fhir.instance.model.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.List; + +public interface IBaseHasExtensions { + + public List extends IBaseExtension>> getExtension(); + + public IBaseExtension> addExtension(); + +} diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java new file mode 100644 index 00000000000..fb8d57ac199 --- /dev/null +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java @@ -0,0 +1,31 @@ +package org.hl7.fhir.instance.model.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.List; + +public interface IBaseHasModifierExtensions { + + public List extends IBaseExtension>> getModifierExtension(); + + public IBaseExtension> addModifierExtension(); + +} diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseIntegerDatatype.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseIntegerDatatype.java index 4965c7c770e..9b592530aea 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseIntegerDatatype.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseIntegerDatatype.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.IPrimitiveType; public interface IBaseIntegerDatatype extends IPrimitiveType { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/ICoding.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/ICoding.java index c3f14f242a0..303394f35d2 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/ICoding.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/ICoding.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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% + */ + public interface ICoding { ICoding setSystem(String theScheme); diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDatatypeElement.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDatatypeElement.java index 292aaf9b92a..eb72ae9025f 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDatatypeElement.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDatatypeElement.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 ca.uhn.fhir.model.api.IElement; public interface IDatatypeElement extends IElement { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDomainResource.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDomainResource.java index a60d3bb9c2b..072b3c37eb6 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDomainResource.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IDomainResource.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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.List; public interface IDomainResource { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java index c998d6cc072..6e704aea7b3 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IIdType.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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% + */ + public interface IIdType { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IMetaType.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IMetaType.java index 5c7b61d74fe..af3a21bffe7 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IMetaType.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IMetaType.java @@ -1,8 +1,30 @@ package org.hl7.fhir.instance.model.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.Date; -public interface IMetaType { +import org.hl7.fhir.instance.model.ICompositeType; + +public interface IMetaType extends ICompositeType { ICoding addTag(); diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/INarrative.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/INarrative.java index 58f89e95eca..ea3fab02975 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/INarrative.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/INarrative.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.ICompositeType; public interface INarrative extends ICompositeType { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IReference.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IReference.java index dc50300ec09..0926349fe02 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IReference.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IReference.java @@ -1,5 +1,25 @@ package org.hl7.fhir.instance.model.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 org.hl7.fhir.instance.model.IBase; public interface IReference extends IBase { diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 5877c7e53c1..5df2930f93a 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -24,7 +24,16 @@ ca.uhn.fhir.validation.FhirValidator.noPhlocError=Phloc-schematron library not f # JPA Messages -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request. -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided -ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} +ca.uhn.fhir.jpa.dao.BaseFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request. +ca.uhn.fhir.jpa.dao.BaseFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources +ca.uhn.fhir.jpa.dao.BaseFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided +ca.uhn.fhir.jpa.dao.BaseFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} + +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionContainsMultipleWithDuplicateId=Transaction bundle contains multiple resources with ID: {0} +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionEntryHasInvalidVerb=Transaction bundle entry has missing or invalid HTTP Verb specified in Bundle.entry.transaction.method. Found value: "{0}" +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionMissingUrl=Unable to perform {0}, no URL provided. +ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1} + +ca.uhn.fhir.jpa.dao.FhirResourceDao.duplicateCreateForcedId=Can not create entity with ID[{0}], a resource with this ID already exists +ca.uhn.fhir.jpa.dao.FhirResourceDao.failedToCreateWithClientAssignedNumericId=Can not create entity with ID[{0}], this server does not allow clients to assign numeric IDs +ca.uhn.fhir.jpa.dao.FhirResourceDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed. diff --git a/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/parser/MultiVersionJsonParserTest.java b/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/parser/MultiVersionJsonParserTest.java index a51236d25ff..f11e483cad1 100644 --- a/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/parser/MultiVersionJsonParserTest.java +++ b/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/parser/MultiVersionJsonParserTest.java @@ -21,8 +21,8 @@ public class MultiVersionJsonParserTest { String str = FhirContext.forDstu2().newJsonParser().encodeResourceToString(p); ourLog.info(str); - - assertThat(str,StringContains.containsString("{\"resourceType\":\"Patient\",\"http://foo#ext\":[{\"valueQuantity\":{\"value\":2.2}}],\"identifier\":[{\"system\":\"urn:sys\",\"value\":\"001\"}]}")); + + assertThat(str, StringContains.containsString("{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://foo#ext\",\"valueQuantity\":{\"value\":2.2}}],\"identifier\":[{\"system\":\"urn:sys\",\"value\":\"001\"}]}")); } } diff --git a/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java b/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java index 0a591bc2443..f83848dd45b 100644 --- a/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java +++ b/hapi-fhir-base/testmindeps/src/test/java/ca/uhn/fhir/testmindeps/ValidatorTest.java @@ -9,22 +9,23 @@ import ca.uhn.fhir.validation.FhirValidator; public class ValidatorTest { - @Test - public void testValidator() { - - FhirContext ctx = new FhirContext(); - FhirValidator val = ctx.newValidator(); - - // Phloc is not onthe classpath - assertTrue(val.isValidateAgainstStandardSchema()); - assertFalse(val.isValidateAgainstStandardSchematron()); + @Test + public void testValidator() { + + FhirContext ctx = new FhirContext(); + FhirValidator val = ctx.newValidator(); + + // Phloc is not onthe classpath + assertTrue(val.isValidateAgainstStandardSchema()); + assertFalse(val.isValidateAgainstStandardSchematron()); + + try { + val.setValidateAgainstStandardSchematron(true); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Phloc-schematron library not found on classpath, can not enable perform schematron validation", e.getMessage()); + } + + } - try { val.setValidateAgainstStandardSchematron(true); - fail(); - } catch (IllegalArgumentException e) { - assertEquals("Phloc-schematron library not found on classpath, can not enable perform schematron validation", e.getMessage()); - } - - } - } diff --git a/hapi-fhir-jpaserver-base/.classpath b/hapi-fhir-jpaserver-base/.classpath index 9ebe1a6b158..f9148a1a7ac 100644 --- a/hapi-fhir-jpaserver-base/.classpath +++ b/hapi-fhir-jpaserver-base/.classpath @@ -27,7 +27,7 @@ - + diff --git a/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs b/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs index d535869bf65..42246e57dab 100644 --- a/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs +++ b/hapi-fhir-jpaserver-base/.settings/org.eclipse.jdt.core.prefs @@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -97,4 +97,4 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml b/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml index 5c9bd7532ab..4f92af543f1 100644 --- a/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml +++ b/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -1,5 +1,5 @@ diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java index a8f1eb5d37b..498cfe17aec 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java @@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.dao; import static org.apache.commons.lang3.StringUtils.*; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.text.Normalizer; import java.util.ArrayList; import java.util.Collection; @@ -48,6 +50,8 @@ import javax.persistence.criteria.Root; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; import org.hl7.fhir.instance.model.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.PlatformTransactionManager; @@ -77,6 +81,7 @@ import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.util.StopWatch; +import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; @@ -87,6 +92,8 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.method.MethodUtil; +import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -95,15 +102,16 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.FhirTerser; import com.google.common.base.Function; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; public abstract class BaseFhirDao implements IDao { - public static final String UCUM_NS = "http://unitsofmeasure.org"; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirDao.class); + private static final Map - + ourRetrievalContexts = new HashMap (); + public static final String UCUM_NS = "http://unitsofmeasure.org"; @Autowired(required = true) private DaoConfig myConfig; @@ -114,8 +122,6 @@ public abstract class BaseFhirDao implements IDao { private EntityManager myEntityManager; private List myListeners = new ArrayList (); - private ISearchParamExtractor mySearchParamExtractor; - @Autowired private PlatformTransactionManager myPlatformTransactionManager; @@ -124,6 +130,8 @@ public abstract class BaseFhirDao implements IDao { private Map , IFhirResourceDao>> myResourceTypeToDao; + private ISearchParamExtractor mySearchParamExtractor; + protected void createForcedIdIfNeeded(ResourceTable entity, IdDt id) { if (id.isEmpty() == false && id.hasIdPart()) { if (isValidPid(id)) { @@ -196,7 +204,7 @@ public abstract class BaseFhirDao implements IDao { b.append(nextValue.getReference().getResourceType()); b.append("] - Valid resource types for this server: "); b.append(myResourceTypeToDao.keySet().toString()); - + throw new InvalidRequestException(b.toString()); } Long valueOf; @@ -508,19 +516,6 @@ public abstract class BaseFhirDao implements IDao { return retVal; } - protected static String normalizeString(String theString) { - char[] out = new char[theString.length()]; - theString = Normalizer.normalize(theString, Normalizer.Form.NFD); - int j = 0; - for (int i = 0, n = theString.length(); i < n; ++i) { - char c = theString.charAt(i); - if (c <= '\u007F') { - out[j++] = c; - } - } - return new String(out).toUpperCase(); - } - protected void notifyWriteCompleted() { for (IDaoListener next : myListeners) { next.writeCompleted(); @@ -579,6 +574,58 @@ public abstract class BaseFhirDao implements IDao { } + protected Set processMatchUrl(String theMatchUrl, Class extends IBaseResource> theResourceType) { + RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); + + SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef); + + IFhirResourceDao extends IResource> dao = getDao(theResourceType); + Set ids = dao.searchForIdsWithAndOr(paramMap); + + return ids; + } + + protected SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) { + SearchParameterMap paramMap = new SearchParameterMap(); + List parameters; + try { + parameters = URLEncodedUtils.parse(new URI(theMatchUrl), "UTF-8"); + } catch (URISyntaxException e) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: " + e.toString()); + } + + ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); + for (NameValuePair next : parameters) { + String paramName = next.getName(); + String qualifier = null; + for (int i = 0; i < paramMap.size(); i++) { + switch (paramName.charAt(i)) { + case '.': + case ':': + qualifier = paramName.substring(i); + paramName = paramName.substring(0, i); + i = Integer.MAX_VALUE; + break; + } + } + + QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue()); + nameToParamLists.put(paramName, paramList); + } + + for (String nextParamName : nameToParamLists.keySet()) { + RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); + if (paramDef == null) { + throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); + } + + List paramList = nameToParamLists.get(nextParamName); + IQueryParameterAnd> param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); + paramMap.add(nextParamName, param); + } + return paramMap; + } + @Override public void registerDaoListener(IDaoListener theListener) { Validate.notNull(theListener, "theListener"); @@ -832,7 +879,20 @@ public abstract class BaseFhirDao implements IDao { } } + protected String translatePidIdToForcedId(Long theId) { + ForcedId forcedId = myEntityManager.find(ForcedId.class, theId); + if (forcedId != null) { + return forcedId.getForcedId(); + } else { + return theId.toString(); + } + } + protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull) { + return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true, true); + } + + protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion) { if (entity.getPublished() == null) { entity.setPublished(new Date()); } @@ -849,8 +909,10 @@ public abstract class BaseFhirDao implements IDao { myEntityManager.persist(historyEntry); } - entity.setVersion(entity.getVersion() + 1); - + if (theUpdateVersion) { + entity.setVersion(entity.getVersion() + 1); + } + Collection paramsString = new ArrayList (entity.getParamsString()); Collection paramsToken = new ArrayList (entity.getParamsToken()); Collection paramsNumber = new ArrayList (entity.getParamsNumber()); @@ -858,12 +920,13 @@ public abstract class BaseFhirDao implements IDao { Collection paramsDate = new ArrayList (entity.getParamsDate()); Collection resourceLinks = new ArrayList (entity.getResourceLinks()); - final List stringParams; - final List tokenParams; - final List numberParams; - final List quantityParams; - final List dateParams; - final List links; + List stringParams = null; + List tokenParams = null; + List numberParams = null; + List quantityParams = null; + List dateParams = null; + List links = null; + if (theDeletedTimestampOrNull != null) { stringParams = Collections.emptyList(); @@ -875,7 +938,7 @@ public abstract class BaseFhirDao implements IDao { entity.setDeleted(theDeletedTimestampOrNull); entity.setUpdated(theDeletedTimestampOrNull); - } else { + } else if (thePerformIndexing) { stringParams = extractSearchParamStrings(entity, theResource); numberParams = extractSearchParamNumber(entity, theResource); @@ -893,7 +956,6 @@ public abstract class BaseFhirDao implements IDao { links = extractResourceLinks(entity, theResource); populateResourceIntoEntity(theResource, entity); - entity.setUpdated(new Date()); entity.setLanguage(theResource.getLanguage().getValue()); entity.setParamsString(stringParams); @@ -909,6 +971,12 @@ public abstract class BaseFhirDao implements IDao { entity.setResourceLinks(links); entity.setHasLinks(links.isEmpty() == false); + } else { + + populateResourceIntoEntity(theResource, entity); + entity.setUpdated(new Date()); + entity.setLanguage(theResource.getLanguage().getValue()); + } if (entity.getId() == null) { @@ -922,59 +990,63 @@ public abstract class BaseFhirDao implements IDao { entity = myEntityManager.merge(entity); } - if (entity.isParamsStringPopulated()) { - for (ResourceIndexedSearchParamString next : paramsString) { - myEntityManager.remove(next); - } - } - for (ResourceIndexedSearchParamString next : stringParams) { - myEntityManager.persist(next); - } + if (thePerformIndexing) { - if (entity.isParamsTokenPopulated()) { - for (ResourceIndexedSearchParamToken next : paramsToken) { - myEntityManager.remove(next); + if (entity.isParamsStringPopulated()) { + for (ResourceIndexedSearchParamString next : paramsString) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamString next : stringParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamToken next : tokenParams) { - myEntityManager.persist(next); - } - if (entity.isParamsNumberPopulated()) { - for (ResourceIndexedSearchParamNumber next : paramsNumber) { - myEntityManager.remove(next); + if (entity.isParamsTokenPopulated()) { + for (ResourceIndexedSearchParamToken next : paramsToken) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamToken next : tokenParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamNumber next : numberParams) { - myEntityManager.persist(next); - } - if (entity.isParamsQuantityPopulated()) { - for (ResourceIndexedSearchParamQuantity next : paramsQuantity) { - myEntityManager.remove(next); + if (entity.isParamsNumberPopulated()) { + for (ResourceIndexedSearchParamNumber next : paramsNumber) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamNumber next : numberParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamQuantity next : quantityParams) { - myEntityManager.persist(next); - } - if (entity.isParamsDatePopulated()) { - for (ResourceIndexedSearchParamDate next : paramsDate) { - myEntityManager.remove(next); + if (entity.isParamsQuantityPopulated()) { + for (ResourceIndexedSearchParamQuantity next : paramsQuantity) { + myEntityManager.remove(next); + } + } + for (ResourceIndexedSearchParamQuantity next : quantityParams) { + myEntityManager.persist(next); } - } - for (ResourceIndexedSearchParamDate next : dateParams) { - myEntityManager.persist(next); - } - if (entity.isHasLinks()) { - for (ResourceLink next : resourceLinks) { - myEntityManager.remove(next); + if (entity.isParamsDatePopulated()) { + for (ResourceIndexedSearchParamDate next : paramsDate) { + myEntityManager.remove(next); + } } - } - for (ResourceLink next : links) { - myEntityManager.persist(next); - } + for (ResourceIndexedSearchParamDate next : dateParams) { + myEntityManager.persist(next); + } + + if (entity.isHasLinks()) { + for (ResourceLink next : resourceLinks) { + myEntityManager.remove(next); + } + } + for (ResourceLink next : links) { + myEntityManager.persist(next); + } + + } // if thePerformIndexing myEntityManager.flush(); @@ -985,4 +1057,17 @@ public abstract class BaseFhirDao implements IDao { return entity; } + protected static String normalizeString(String theString) { + char[] out = new char[theString.length()]; + theString = Normalizer.normalize(theString, Normalizer.Form.NFD); + int j = 0; + for (int i = 0, n = theString.length(); i < n; ++i) { + char c = theString.charAt(i); + if (c <= '\u007F') { + out[j++] = c; + } + } + return new String(out).toUpperCase(); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java index 2e8c353673c..8a82b7d6c74 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirSystemDao.java @@ -20,11 +20,8 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import java.net.URI; -import java.net.URISyntaxException; import java.util.Date; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; @@ -36,27 +33,14 @@ import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.hl7.fhir.instance.model.IBaseResource; - -import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.util.StopWatch; -import ca.uhn.fhir.model.api.IQueryParameterAnd; -import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; -import ca.uhn.fhir.rest.method.MethodUtil; -import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import com.google.common.collect.ArrayListMultimap; - public abstract class BaseFhirSystemDao extends BaseFhirDao implements IFhirSystemDao { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseFhirSystemDao.class); @@ -82,53 +66,6 @@ public abstract class BaseFhirSystemDao extends BaseFhirDao implements IFhirS return myEntityManager.find(ResourceTable.class, candidateMatches.iterator().next()); } - protected Set processMatchUrl(String theMatchUrl, Class extends IBaseResource> theResourceType) { - RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); - - SearchParameterMap paramMap = new SearchParameterMap(); - List parameters; - try { - parameters = URLEncodedUtils.parse(new URI(theMatchUrl), "UTF-8"); - } catch (URISyntaxException e) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: " + e.toString()); - } - - ArrayListMultimap nameToParamLists = ArrayListMultimap.create(); - for (NameValuePair next : parameters) { - String paramName = next.getName(); - String qualifier = null; - for (int i = 0; i < paramMap.size(); i++) { - switch (paramName.charAt(i)) { - case '.': - case ':': - qualifier = paramName.substring(i); - paramName = paramName.substring(0, i); - i = Integer.MAX_VALUE; - break; - } - } - - QualifiedParamList paramList = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(qualifier, next.getValue()); - nameToParamLists.put(paramName, paramList); - } - - for (String nextParamName : nameToParamLists.keySet()) { - RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); - if (paramDef == null) { - throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); - } - - List paramList = nameToParamLists.get(nextParamName); - IQueryParameterAnd> param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); - paramMap.add(nextParamName, param); - } - - IFhirResourceDao extends IResource> dao = getDao(theResourceType); - Set ids = dao.searchForIdsWithAndOr(paramMap); - - return ids; - } - @Override public IBundleProvider history(Date theSince) { StopWatch w = new StopWatch(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java new file mode 100644 index 00000000000..d8c8b30be33 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoMethodOutcome.java @@ -0,0 +1,56 @@ +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 ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.api.MethodOutcome; + +public class DaoMethodOutcome extends MethodOutcome { + + private ResourceTable myEntity; + private IResource myResource; + + public ResourceTable getEntity() { + return myEntity; + } + + public IResource getResource() { + return myResource; + } + + @Override + public DaoMethodOutcome setCreated(Boolean theCreated) { + super.setCreated(theCreated); + return this; + } + + public DaoMethodOutcome setEntity(ResourceTable theEntity) { + myEntity = theEntity; + return this; + } + + public DaoMethodOutcome setResource(IResource theResource) { + myResource = theResource; + return this; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index ffa9690f863..98708c725e6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -20,8 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; import java.math.BigDecimal; import java.util.ArrayList; @@ -97,7 +96,6 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; -import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; @@ -111,6 +109,7 @@ import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; @@ -132,658 +131,6 @@ public class FhirResourceDao extends BaseFhirDao implements private Class myResourceType; private String mySecondaryPrimaryKeyParamName; - @Override - public void addTag(IdDt theId, String theScheme, String theTerm, String theLabel) { - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - - for (BaseTag next : new ArrayList (entity.getTags())) { - if (ObjectUtil.equals(next.getTag().getScheme(), theScheme) && ObjectUtil.equals(next.getTag().getTerm(), theTerm)) { - return; - } - } - - entity.setHasTags(true); - - TagDefinition def = getTag(theScheme, theTerm, theLabel); - BaseTag newEntity = entity.addTag(def); - - myEntityManager.persist(newEntity); - myEntityManager.merge(entity); - notifyWriteCompleted(); - ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() }); - } - - @Override - public MethodOutcome create(final T theResource) { - StopWatch w = new StopWatch(); - ResourceTable entity = new ResourceTable(); - entity.setResourceType(toResourceName(theResource)); - - if (theResource.getId().isEmpty() == false) { - if (isValidPid(theResource.getId())) { - throw new UnprocessableEntityException( - "This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); - } - createForcedIdIfNeeded(entity, theResource.getId()); - - if (entity.getForcedId() != null) { - try { - translateForcedIdToPid(theResource.getId()); - throw new UnprocessableEntityException("Can not create entity with ID[" + theResource.getId().getValue() + "], constraint violation occurred"); - } catch (ResourceNotFoundException e) { - // good, this ID doesn't exist so we can create it - } - } - - } - - updateEntity(theResource, entity, false, null); - - MethodOutcome outcome = toMethodOutcome(entity); - notifyWriteCompleted(); - ourLog.info("Processed create on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return outcome; - } - - @Override - public MethodOutcome delete(IdDt theId) { - StopWatch w = new StopWatch(); - final ResourceTable entity = readEntityLatestVersion(theId); - if (theId.hasVersionIdPart() && theId.getVersionIdPartAsLong().longValue() != entity.getVersion()) { - throw new InvalidRequestException("Trying to update " + theId + " but this is not the current version"); - } - - ResourceTable savedEntity = updateEntity(null, entity, true, new Date()); - - notifyWriteCompleted(); - - ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return toMethodOutcome(savedEntity); - } - - // private Set addPredicateComposite(String theParamName, Set thePids, List extends - // IQueryParameterType> theList) { - // } - - @Override - public TagList getAllResourceTags() { - StopWatch w = new StopWatch(); - TagList tags = super.getTags(myResourceType, null); - ourLog.info("Processed getTags on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return tags; - } - - public Class getResourceType() { - return myResourceType; - } - - @Override - public TagList getTags(IdDt theResourceId) { - StopWatch w = new StopWatch(); - TagList retVal = super.getTags(myResourceType, theResourceId); - ourLog.info("Processed getTags on {} in {}ms", theResourceId, w.getMillisAndRestart()); - return retVal; - } - - @Override - public IBundleProvider history(Date theSince) { - StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(myResourceName, null, theSince); - ourLog.info("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); - return retVal; - } - - @Override - public IBundleProvider history(final IdDt theId, final Date theSince) { - final InstantDt end = createHistoryToTimestamp(); - final String resourceType = getContext().getResourceDefinition(myResourceType).getName(); - - T currentTmp; - try { - currentTmp = read(theId.toVersionless()); - if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) { - currentTmp = null; - } - } catch (ResourceNotFoundException e) { - currentTmp = null; - } - - final T current = currentTmp; - - String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" - + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); - TypedQuery countQuery = myEntityManager.createQuery(querySring, Long.class); - countQuery.setParameter("PID", translateForcedIdToPid(theId)); - countQuery.setParameter("RESTYPE", resourceType); - countQuery.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); - if (theSince != null) { - countQuery.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); - } - int historyCount = countQuery.getSingleResult().intValue(); - - final int offset; - final int count; - if (current != null) { - count = historyCount + 1; - offset = 1; - } else { - offset = 0; - count = historyCount; - } - - if (count == 0) { - throw new ResourceNotFoundException(theId); - } - - return new IBundleProvider() { - - @Override - public InstantDt getPublished() { - return end; - } - - @Override - public List getResources(int theFromIndex, int theToIndex) { - ArrayList retVal = new ArrayList (); - if (theFromIndex == 0 && current != null) { - retVal.add(current); - } - - TypedQuery q = myEntityManager.createQuery( - "SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " - + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); - q.setParameter("PID", translateForcedIdToPid(theId)); - q.setParameter("RESTYPE", resourceType); - q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); - if (theSince != null) { - q.setParameter("SINCE", theSince, TemporalType.TIMESTAMP); - } - - q.setFirstResult(Math.max(0, theFromIndex - offset)); - q.setMaxResults(theToIndex - theFromIndex); - - List results = q.getResultList(); - for (ResourceHistoryTable next : results) { - if (retVal.size() == (theToIndex - theFromIndex)) { - break; - } - retVal.add(toResource(myResourceType, next)); - } - - return retVal; - } - - @Override - public int size() { - return count; - } - }; - - } - - @Override - public IBundleProvider history(Long theId, Date theSince) { - StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(myResourceName, theId, theSince); - ourLog.info("Processed history on {} in {}ms", theId, w.getMillisAndRestart()); - return retVal; - } - - @PostConstruct - public void postConstruct() { - RuntimeResourceDefinition def = getContext().getResourceDefinition(myResourceType); - myResourceName = def.getName(); - - if (mySecondaryPrimaryKeyParamName != null) { - RuntimeSearchParam sp = def.getSearchParam(mySecondaryPrimaryKeyParamName); - if (sp == null) { - throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); - } - if (sp.getParamType() != SearchParamTypeEnum.TOKEN) { - throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName - + "] is not a token type, only token is supported"); - } - } - - } - - @Override - public T read(IdDt theId) { - validateResourceTypeAndThrowIllegalArgumentException(theId); - - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - validateResourceType(entity); - - T retVal = toResource(myResourceType, entity); - - InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal); - if (deleted != null && !deleted.isEmpty()) { - throw new ResourceGoneException("Resource was deleted at " + deleted.getValueAsString()); - } - - ourLog.info("Processed read on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - return retVal; - } - - @Override - public BaseHasResource readEntity(IdDt theId) { - boolean checkForForcedId = true; - - BaseHasResource entity = readEntity(theId, checkForForcedId); - - return entity; - } - - @Override - public BaseHasResource readEntity(IdDt theId, boolean theCheckForForcedId) { - validateResourceTypeAndThrowIllegalArgumentException(theId); - - Long pid = translateForcedIdToPid(theId); - BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid); - if (theId.hasVersionIdPart()) { - if (entity.getVersion() != theId.getVersionIdPartAsLong()) { - entity = null; - } - } - - if (entity == null) { - if (theId.hasVersionIdPart()) { - TypedQuery q = myEntityManager.createQuery( - "SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); - q.setParameter("RID", pid); - q.setParameter("RTYP", myResourceName); - q.setParameter("RVER", theId.getVersionIdPartAsLong()); - entity = q.getSingleResult(); - } - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - } - - - validateResourceType(entity); - - if (theCheckForForcedId) { - validateGivenIdIsAppropriateToRetrieveResource(theId, entity); - } - return entity; - } - - @Override - public void removeTag(IdDt theId, String theScheme, String theTerm) { - StopWatch w = new StopWatch(); - BaseHasResource entity = readEntity(theId); - if (entity == null) { - throw new ResourceNotFoundException(theId); - } - - for (BaseTag next : new ArrayList (entity.getTags())) { - if (next.getTag().getScheme().equals(theScheme) && next.getTag().getTerm().equals(theTerm)) { - myEntityManager.remove(next); - entity.getTags().remove(next); - } - } - - if (entity.getTags().isEmpty()) { - entity.setHasTags(false); - } - - myEntityManager.merge(entity); - - ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() }); - } - - @Override - public IBundleProvider search(Map theParams) { - SearchParameterMap map = new SearchParameterMap(); - for (Entry nextEntry : theParams.entrySet()) { - map.add(nextEntry.getKey(), (nextEntry.getValue())); - } - return search(map); - } - - @Override - public IBundleProvider search(final SearchParameterMap theParams) { - StopWatch w = new StopWatch(); - final InstantDt now = InstantDt.withCurrentTime(); - - Set loadPids; - if (theParams.isEmpty()) { - loadPids = new HashSet (); - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createTupleQuery(); - Root from = cq.from(ResourceTable.class); - cq.multiselect(from.get("myId").as(Long.class)); - cq.where(builder.equal(from.get("myResourceType"), myResourceName)); - - TypedQuery query = myEntityManager.createQuery(cq); - for (Tuple next : query.getResultList()) { - loadPids.add(next.get(0, Long.class)); - } - } else { - loadPids = searchForIdsWithAndOr(theParams); - if (loadPids.isEmpty()) { - return new SimpleBundleProvider(); - } - } - - final List pids = new ArrayList (loadPids); - - // Handle sorting if any was provided - if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { - List orders = new ArrayList (); - List predicates = new ArrayList (); - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createTupleQuery(); - Root from = cq.from(ResourceTable.class); - predicates.add(from.get("myId").in(pids)); - createSort(builder, from, theParams.getSort(), orders, predicates); - if (orders.size() > 0) { - loadPids = new LinkedHashSet (); - cq.multiselect(from.get("myId").as(Long.class)); - cq.where(predicates.toArray(new Predicate[0])); - cq.orderBy(orders); - - TypedQuery query = myEntityManager.createQuery(cq); - - for (Tuple next : query.getResultList()) { - loadPids.add(next.get(0, Long.class)); - } - - ourLog.info("Sort PID order is now: {}", loadPids); - - pids.clear(); - pids.addAll(loadPids); - } - } - - IBundleProvider retVal = new IBundleProvider() { - - @Override - public InstantDt getPublished() { - return now; - } - - @Override - public List getResources(final int theFromIndex, final int theToIndex) { - TransactionTemplate template = new TransactionTemplate(myPlatformTransactionManager); - return template.execute(new TransactionCallback >() { - @Override - public List
doInTransaction(TransactionStatus theStatus) { - List pidsSubList = pids.subList(theFromIndex, theToIndex); - - // Execute the query and make sure we return distinct results - List retVal = new ArrayList