Merge branch 'master' of github.com:jamesagnew/hapi-fhir
This commit is contained in:
commit
04bc96ff8e
|
@ -59,6 +59,8 @@
|
|||
<version>2.0.2.RELEASE</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!--
|
||||
-->
|
||||
|
||||
<!-- Only required for narrative generator support -->
|
||||
<dependency>
|
||||
|
@ -253,6 +255,7 @@
|
|||
<version>${hamcrest_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-web</artifactId>
|
||||
|
@ -269,8 +272,9 @@
|
|||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-jwt</artifactId>
|
||||
<version>1.0.2.RELEASE</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
-->
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
|
|
@ -36,12 +36,34 @@
|
|||
Add documentation on how to use eBay CORS Filter to support Cross Origin Resource
|
||||
Sharing (CORS) to server. CORS support that was built in to the server itself has
|
||||
been removed, as it did not work correctly (and was reinventing a wheel that others
|
||||
have done a great job inventing).
|
||||
have done a great job inventing). Thanks to Peter Bernhardt of Relay Health for all the assistance
|
||||
in testing this!
|
||||
</action>
|
||||
<action type="fix">
|
||||
IResource interface did not expose the getLanguage/setLanguage methods from BaseResource,
|
||||
so the resource language was difficult to access.
|
||||
</action>
|
||||
<action type="fix">
|
||||
JSON Parser now gives a more friendly error message if it tries to parse JSON with invalid use
|
||||
of single quotes
|
||||
</action>
|
||||
<action type="add">
|
||||
Transaction server method is now allowed to return an OperationOutcome in addition to the
|
||||
incoming resources. The public test server now does this in ordeer to return status information
|
||||
about the transaction processing.
|
||||
</action>
|
||||
<action type="add">
|
||||
Update method in the server can now flag (via a field on the MethodOutcome object being returned)
|
||||
that the result was actually a creation, and Create method can indicate that it was actually an
|
||||
update. This has no effect other than to switch between the HTTP 200 and HTTP 201 status codes on the
|
||||
response, but this may be useful in some circumstances.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Annotation client search methods with a specific resource type (e.g. List<Patient> search())
|
||||
won't return any resources that aren't of the correct type that are received in a response
|
||||
bundle (generally these are referenced resources, so they are populated in the reference fields instead).
|
||||
Thanks to Tahura Chaudhry of University Health Network for the unit test!
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.5" date="2014-Jul-30">
|
||||
<action type="add">
|
||||
|
|
|
@ -37,6 +37,8 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
public class Bundle extends BaseBundle /* implements IElement */{
|
||||
|
@ -103,6 +105,31 @@ public class Bundle extends BaseBundle /* implements IElement */{
|
|||
return map.get(theId.toUnqualified());
|
||||
}
|
||||
|
||||
// public static void main(String[] args) {
|
||||
//
|
||||
// FhirContext ctx = new FhirContext();
|
||||
// String txt = "<Organization xmlns=\"http://hl7.org/fhir\">\n" +
|
||||
// " <extension url=\"http://fhir.connectinggta.ca/Profile/organization#providerIdPool\">\n" +
|
||||
// " <valueUri value=\"urn:oid:2.16.840.1.113883.3.239.23.21.1\"/>\n" +
|
||||
// " </extension>\n" +
|
||||
// " <text>\n" +
|
||||
// " <status value=\"generated\"/>\n" +
|
||||
// " <div xmlns=\"http://www.w3.org/1999/xhtml\"/>\n" +
|
||||
// " </text>\n" +
|
||||
// " <identifier>\n" +
|
||||
// " <use value=\"official\"/>\n" +
|
||||
// " <label value=\"HSP 2.16.840.1.113883.3.239.23.21\"/>\n" +
|
||||
// " <system value=\"urn:cgta:hsp_ids\"/>\n" +
|
||||
// " <value value=\"urn:oid:2.16.840.1.113883.3.239.23.21\"/>\n" +
|
||||
// " </identifier>\n" +
|
||||
// " <name value=\"火星第五人民医院\"/>\n" +
|
||||
// "</Organization>";
|
||||
//
|
||||
// IGenericClient c = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/base");
|
||||
// c.registerInterceptor(new LoggingInterceptor(true));
|
||||
// c.update().resource(txt).withId("1665").execute();
|
||||
// }
|
||||
//
|
||||
public List<BundleEntry> getEntries() {
|
||||
if (myEntries == null) {
|
||||
myEntries = new ArrayList<BundleEntry>();
|
||||
|
@ -248,14 +275,15 @@ public class Bundle extends BaseBundle /* implements IElement */{
|
|||
|
||||
RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
|
||||
|
||||
if (theResource.getId() != null && StringUtils.isNotBlank(theResource.getId().getValue())) {
|
||||
String title = ResourceMetadataKeyEnum.TITLE.get(theResource);
|
||||
if (title != null) {
|
||||
entry.getTitle().setValue(title);
|
||||
} else {
|
||||
entry.getTitle().setValue(def.getName() + " " + theResource.getId().getValue());
|
||||
entry.getTitle().setValue(def.getName() + " " + StringUtils.defaultString(theResource.getId().getValue(), "(no ID)"));
|
||||
}
|
||||
|
||||
if (theResource.getId() != null && StringUtils.isNotBlank(theResource.getId().getValue())) {
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append(theServerBase);
|
||||
if (b.length() > 0 && b.charAt(b.length() - 1) != '/') {
|
||||
|
|
|
@ -162,15 +162,6 @@ public class IdDt extends BasePrimitive<String> {
|
|||
return ObjectUtils.equals(getResourceType(), theId.getResourceType()) && ObjectUtils.equals(getIdPart(), theId.getIdPart()) && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to <code>this</code> IdDt. It is generally not neccesary to use this method but it is
|
||||
* provided for consistency with the rest of the API.
|
||||
*/
|
||||
@Override
|
||||
public IdDt getId() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getIdPart() {
|
||||
return myUnqualifiedId;
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import javax.json.JsonValue;
|
|||
import javax.json.JsonValue.ValueType;
|
||||
import javax.json.stream.JsonGenerator;
|
||||
import javax.json.stream.JsonGeneratorFactory;
|
||||
import javax.json.stream.JsonParsingException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -135,6 +136,10 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
}
|
||||
|
||||
private void assertObjectOfType(JsonValue theResourceTypeObj, ValueType theValueType, String thePosition) {
|
||||
if (theResourceTypeObj == null) {
|
||||
throw new DataFormatException("Invalid JSON content detected, missing required element: '" + thePosition + "'");
|
||||
}
|
||||
|
||||
if (theResourceTypeObj.getValueType() != theValueType) {
|
||||
throw new DataFormatException("Invalid content of element " + thePosition + ", expected " + theValueType);
|
||||
}
|
||||
|
@ -620,9 +625,18 @@ public class JsonParser extends BaseParser implements IParser {
|
|||
|
||||
@Override
|
||||
public <T extends IResource> Bundle parseBundle(Class<T> theResourceType, Reader theReader) {
|
||||
JsonReader reader = Json.createReader(theReader);
|
||||
JsonObject object = reader.readObject();
|
||||
JsonReader reader;
|
||||
JsonObject object;
|
||||
|
||||
try {
|
||||
reader = Json.createReader(theReader);
|
||||
object = reader.readObject();
|
||||
} catch (JsonParsingException e) {
|
||||
if (e.getMessage().startsWith("Unexpected char 39")) {
|
||||
throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage() + " - This may indicate that single quotes are being used as JSON escapes where double quotes are required", e);
|
||||
}
|
||||
throw new DataFormatException("Failed to parse JSON encoded FHIR content: " + e.getMessage(), e);
|
||||
}
|
||||
JsonValue resourceTypeObj = object.get("resourceType");
|
||||
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
|
||||
String resourceType = ((JsonString) resourceTypeObj).getString();
|
||||
|
|
|
@ -28,19 +28,72 @@ public class MethodOutcome {
|
|||
private IdDt myId;
|
||||
private OperationOutcome myOperationOutcome;
|
||||
private IdDt myVersionId;
|
||||
private Boolean myCreated;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public MethodOutcome() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theId
|
||||
* The ID of the created/updated resource
|
||||
*/
|
||||
public MethodOutcome(IdDt theId) {
|
||||
myId = theId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theId
|
||||
* The ID of the created/updated resource
|
||||
*
|
||||
* @param theCreated
|
||||
* 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 MethodOutcome(IdDt theId, Boolean theCreated) {
|
||||
myId = theId;
|
||||
myCreated = theCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theId
|
||||
* The ID of the created/updated resource
|
||||
*
|
||||
* @param theOperationOutcome
|
||||
* The operation outcome to return with the response (or null for none)
|
||||
*/
|
||||
public MethodOutcome(IdDt theId, OperationOutcome theOperationOutcome) {
|
||||
myId = theId;
|
||||
myOperationOutcome = theOperationOutcome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param theId
|
||||
* The ID of the created/updated resource
|
||||
*
|
||||
* @param theOperationOutcome
|
||||
* The operation outcome to return with the response (or null for none)
|
||||
*
|
||||
* @param theCreated
|
||||
* 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 MethodOutcome(IdDt theId, OperationOutcome theOperationOutcome, Boolean theCreated) {
|
||||
myId = theId;
|
||||
myOperationOutcome = theOperationOutcome;
|
||||
myCreated = theCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the constructor which accepts a single IdDt parameter, and include the logical ID and version ID in that IdDt instance
|
||||
*/
|
||||
|
@ -63,11 +116,9 @@ public class MethodOutcome {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OperationOutcome} resource to return to the client or
|
||||
* <code>null</code> if none.
|
||||
* Returns the {@link OperationOutcome} resource to return to the client or <code>null</code> if none.
|
||||
*
|
||||
* @return This method <b>will return null</b>, unlike many methods in the
|
||||
* API.
|
||||
* @return This method <b>will return null</b>, unlike many methods in the API.
|
||||
*/
|
||||
public OperationOutcome getOperationOutcome() {
|
||||
return myOperationOutcome;
|
||||
|
@ -80,13 +131,32 @@ public class MethodOutcome {
|
|||
return myVersionId;
|
||||
}
|
||||
|
||||
public Boolean getCreated() {
|
||||
return myCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param theCreated
|
||||
* 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) {
|
||||
myCreated = theCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param theId
|
||||
* The ID of the created/updated resource
|
||||
*/
|
||||
public void setId(IdDt theId) {
|
||||
myId = theId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OperationOutcome} resource to return to the client. Set
|
||||
* to <code>null</code> (which is the default) if none.
|
||||
* Sets the {@link OperationOutcome} resource to return to the client. Set to <code>null</code> (which is the default) if none.
|
||||
*/
|
||||
public void setOperationOutcome(OperationOutcome theOperationOutcome) {
|
||||
myOperationOutcome = theOperationOutcome;
|
||||
|
|
|
@ -745,6 +745,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException {
|
||||
MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
|
||||
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
|
||||
response.setCreated(true);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,8 @@ public interface IGenericClient {
|
|||
* @param theResource
|
||||
* The resource to create
|
||||
* @return An outcome
|
||||
* @deprecated Use {@link #create() fluent method instead}. This method will be removed.
|
||||
*
|
||||
*/
|
||||
MethodOutcome create(IResource theResource);
|
||||
|
||||
|
|
|
@ -157,12 +157,20 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
|||
throw new InternalErrorException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName()
|
||||
+ " returned null, which is not allowed for create operation");
|
||||
}
|
||||
if (response.getCreated() == null || response.getCreated() == Boolean.TRUE) {
|
||||
theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
|
||||
} else {
|
||||
theResponse.setStatus(Constants.STATUS_HTTP_200_OK);
|
||||
}
|
||||
addLocationHeader(theRequest, theResponse, response);
|
||||
break;
|
||||
|
||||
case UPDATE:
|
||||
if (response.getCreated() == null || response.getCreated() == Boolean.FALSE) {
|
||||
theResponse.setStatus(Constants.STATUS_HTTP_200_OK);
|
||||
} else {
|
||||
theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
|
||||
}
|
||||
addLocationHeader(theRequest, theResponse, response);
|
||||
break;
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.method;
|
|||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -48,9 +49,12 @@ 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.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
|
||||
abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> {
|
||||
protected static final Set<String> ALLOWED_PARAMS;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseResourceReturningMethodBinding.class);
|
||||
|
||||
static {
|
||||
HashSet<String> set = new HashSet<String>();
|
||||
set.add(Constants.PARAM_FORMAT);
|
||||
|
@ -58,9 +62,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
set.add(Constants.PARAM_PRETTY);
|
||||
ALLOWED_PARAMS = Collections.unmodifiableSet(set);
|
||||
}
|
||||
|
||||
private MethodReturnTypeEnum myMethodReturnType;
|
||||
private Class<?> myResourceListCollectionType;
|
||||
private String myResourceName;
|
||||
|
||||
private Class<? extends IResource> myResourceType;
|
||||
|
||||
public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||
|
@ -68,7 +73,17 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
|
||||
Class<?> methodReturnType = theMethod.getReturnType();
|
||||
if (Collection.class.isAssignableFrom(methodReturnType)) {
|
||||
|
||||
myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES;
|
||||
Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
|
||||
if (collectionType != null) {
|
||||
if (!Object.class.equals(collectionType) && !IResource.class.isAssignableFrom(collectionType)) {
|
||||
throw new ConfigurationException("Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: "
|
||||
+ collectionType);
|
||||
}
|
||||
}
|
||||
myResourceListCollectionType = collectionType;
|
||||
|
||||
} else if (IResource.class.isAssignableFrom(methodReturnType)) {
|
||||
myMethodReturnType = MethodReturnTypeEnum.RESOURCE;
|
||||
} else if (Bundle.class.isAssignableFrom(methodReturnType)) {
|
||||
|
@ -76,7 +91,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
} else if (IBundleProvider.class.isAssignableFrom(methodReturnType)) {
|
||||
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
|
||||
} else {
|
||||
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
||||
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
|
||||
+ theMethod.getDeclaringClass().getCanonicalName());
|
||||
}
|
||||
|
||||
if (theReturnResourceType != null) {
|
||||
|
@ -118,7 +134,20 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
case BUNDLE:
|
||||
return bundle;
|
||||
case LIST_OF_RESOURCES:
|
||||
return bundle.toListOfResources();
|
||||
List<IResource> listOfResources;
|
||||
if (myResourceListCollectionType != null) {
|
||||
listOfResources = new ArrayList<IResource>();
|
||||
for (IResource next : bundle.toListOfResources()) {
|
||||
if (!myResourceListCollectionType.isAssignableFrom(next.getClass())) {
|
||||
ourLog.debug("Not returning resource of type {} because it is not a subclass or instance of {}", next.getClass(), myResourceListCollectionType);
|
||||
continue;
|
||||
}
|
||||
listOfResources.add(next);
|
||||
}
|
||||
} else {
|
||||
listOfResources = bundle.toListOfResources();
|
||||
}
|
||||
return listOfResources;
|
||||
case RESOURCE:
|
||||
List<IResource> list = bundle.toListOfResources();
|
||||
if (list.size() == 0) {
|
||||
|
@ -199,7 +228,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
IBundleProvider result = invokeServer(theRequest, params);
|
||||
switch (getReturnType()) {
|
||||
case BUNDLE:
|
||||
RestfulServer.streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode, 0, count, null, respondGzip);
|
||||
RestfulServer.streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser,
|
||||
narrativeMode, 0, count, null, respondGzip);
|
||||
break;
|
||||
case RESOURCE:
|
||||
if (result.size() == 0) {
|
||||
|
@ -207,7 +237,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
} else if (result.size() > 1) {
|
||||
throw new InternalErrorException("Method returned multiple resources");
|
||||
}
|
||||
RestfulServer.streamResponseAsResource(theServer, theResponse, result.getResources(0, 1).get(0), responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip, theRequest.getFhirServerBase());
|
||||
RestfulServer.streamResponseAsResource(theServer, theResponse, result.getResources(0, 1).get(0), responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, respondGzip,
|
||||
theRequest.getFhirServerBase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -223,7 +254,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
}
|
||||
|
||||
public enum MethodReturnTypeEnum {
|
||||
BUNDLE, LIST_OF_RESOURCES, RESOURCE, BUNDLE_PROVIDER
|
||||
BUNDLE, BUNDLE_PROVIDER, LIST_OF_RESOURCES, RESOURCE
|
||||
}
|
||||
|
||||
public enum ReturnTypeEnum {
|
||||
|
|
|
@ -67,8 +67,11 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
} else {
|
||||
myDescription = StringUtils.defaultIfBlank(desc.shortDefinition(), null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
|
|
|
@ -32,6 +32,7 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
|
@ -104,11 +105,16 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
|
|||
Object response= invokeServerMethod(theMethodParams);
|
||||
IBundleProvider retVal = toResourceList(response);
|
||||
|
||||
int offset = 0;
|
||||
if (retVal.size() != resources.size()) {
|
||||
if (retVal.size() > 0 && retVal.getResources(0, 1).get(0) instanceof OperationOutcome) {
|
||||
offset = 1;
|
||||
} else {
|
||||
throw new InternalErrorException("Transaction bundle contained " + resources.size() + " entries, but server method response contained " + retVal.size() + " entries (must be the same)");
|
||||
}
|
||||
}
|
||||
|
||||
List<IResource> retResources = retVal.getResources(0, retVal.size());
|
||||
List<IResource> retResources = retVal.getResources(offset, retVal.size());
|
||||
for (int i =0; i < resources.size(); i++) {
|
||||
IdDt oldId = oldIds.get(i);
|
||||
IResource newRes = retResources.get(i);
|
||||
|
@ -117,8 +123,8 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
|
|||
}
|
||||
|
||||
if (oldId != null && !oldId.isEmpty()) {
|
||||
if (!oldId.getId().equals(newRes.getId())) {
|
||||
newRes.getResourceMetadata().put(ResourceMetadataKeyEnum.PREVIOUS_ID, oldId.getId());
|
||||
if (!oldId.equals(newRes.getId())) {
|
||||
newRes.getResourceMetadata().put(ResourceMetadataKeyEnum.PREVIOUS_ID, oldId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1054,9 +1054,11 @@ public class RestfulServer extends HttpServlet {
|
|||
|
||||
for (IResource next : resourceList) {
|
||||
if (next.getId() == null || next.getId().isEmpty()) {
|
||||
if (!(next instanceof OperationOutcome)) {
|
||||
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bundle bundle = createBundleFromResourceList(theServer.getFhirContext(), theServer.getServerName(), resourceList, theServerBase, theCompleteUrl, theResult.size());
|
||||
|
||||
|
|
|
@ -606,6 +606,11 @@ public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient t
|
|||
outcome.addIssue().setDetails("One minor issue detected");
|
||||
retVal.setOperationOutcome(outcome);
|
||||
|
||||
// If your server supports creating resources during an update if they don't already exist
|
||||
// (this is not mandatory and may not be desirable anyhow) you can flag in the response
|
||||
// that this was a creation as follows:
|
||||
// retVal.setCreated(true);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
//END SNIPPET: update
|
||||
|
@ -863,15 +868,13 @@ public List<IResource> transaction(@TransactionParam List<IResource> theResource
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* According to the specification, a bundle must be returned. This bundle will contain
|
||||
* all of the created/updated/deleted resources, including their new/updated identities.
|
||||
*
|
||||
* The returned list must be the exact same size as the list of resources
|
||||
* passed in, and it is acceptable to return the same list instance that was
|
||||
* passed in.
|
||||
*/
|
||||
List<IResource> retVal = theResources;
|
||||
// According to the specification, a bundle must be returned. This bundle will contain
|
||||
// all of the created/updated/deleted resources, including their new/updated identities.
|
||||
//
|
||||
// The returned list must be the exact same size as the list of resources
|
||||
// passed in, and it is acceptable to return the same list instance that was
|
||||
// passed in.
|
||||
List<IResource> retVal = new ArrayList<IResource>(theResources);
|
||||
for (IResource next : theResources) {
|
||||
/*
|
||||
* Populate each returned resource with the new ID for that resource,
|
||||
|
@ -881,6 +884,12 @@ public List<IResource> transaction(@TransactionParam List<IResource> theResource
|
|||
next.setId(newId);
|
||||
}
|
||||
|
||||
// If wanted, you may optionally also return an OperationOutcome resource
|
||||
// If present, the OperationOutcome must come first in the returned list.
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Completed successfully");
|
||||
retVal.add(0, oo);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
//END SNIPPET: transaction
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value></param-value>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
|
|
|
@ -83,6 +83,17 @@ public class JsonParserTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseSingleQuotes() {
|
||||
try {
|
||||
ourCtx.newJsonParser().parseBundle("{ 'resourceType': 'Bundle' }");
|
||||
fail();
|
||||
} catch (DataFormatException e) {
|
||||
// Should be an error message about how single quotes aren't valid JSON
|
||||
assertThat(e.getMessage(), containsString("double quote"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeExtensionInCompositeElement() {
|
||||
|
||||
|
|
|
@ -692,6 +692,8 @@ public class ClientTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchByDateRange() throws Exception {
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -12,6 +12,8 @@ import org.apache.http.Header;
|
|||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpOptions;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
|
@ -26,6 +28,7 @@ import org.junit.Test;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.rest.server.ResfulServerSelfReferenceTest.DummyPatientResourceProvider;
|
||||
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||
|
||||
|
@ -54,6 +57,7 @@ public class CorsTest {
|
|||
fh.setInitParameter("cors.logging.enabled", "true");
|
||||
fh.setInitParameter("cors.allowed.origins", "*");
|
||||
fh.setInitParameter("cors.allowed.headers", "x-fhir-starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers");
|
||||
fh.setInitParameter("cors.exposed.headers", "Location,Content-Location");
|
||||
fh.setInitParameter("cors.allowed.methods", "GET,POST,PUT,DELETE,OPTIONS");
|
||||
|
||||
ServletContextHandler ch = new ServletContextHandler();
|
||||
|
@ -100,6 +104,20 @@ public class CorsTest {
|
|||
|
||||
assertEquals(1, bundle.getEntries().size());
|
||||
}
|
||||
{
|
||||
HttpPost httpOpt = new HttpPost(baseUri + "/Patient");
|
||||
httpOpt.addHeader("Access-Control-Request-Method", "POST");
|
||||
httpOpt.addHeader("Origin", "http://www.fhir-starter.com");
|
||||
httpOpt.addHeader("Access-Control-Request-Headers", "accept, x-fhir-starter, content-type");
|
||||
httpOpt.setEntity(new StringEntity(ourCtx.newXmlParser().encodeResourceToString(new Patient())));
|
||||
HttpResponse status = ourClient.execute(httpOpt);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info("Response: {}", status);
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
assertEquals("POST", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
|
||||
assertEquals("http://www.fhir-starter.com", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue());
|
||||
}
|
||||
} finally {
|
||||
server.stop();
|
||||
}
|
||||
|
|
|
@ -28,10 +28,13 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
|
|||
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.UriDt;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
|
||||
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||
|
||||
|
@ -174,6 +177,11 @@ public class ResfulServerSelfReferenceTest {
|
|||
return idToPatient;
|
||||
}
|
||||
|
||||
@Create
|
||||
public MethodOutcome create(@ResourceParam Patient thePatient) {
|
||||
return new MethodOutcome(thePatient.getId());
|
||||
}
|
||||
|
||||
@Search()
|
||||
public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
|
||||
for (Patient next : getIdToPatient().values()) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.server;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -16,6 +17,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -24,6 +26,7 @@ import ca.uhn.fhir.model.api.Bundle;
|
|||
import ca.uhn.fhir.model.api.BundleEntry;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
|
@ -37,10 +40,19 @@ import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
|||
public class TransactionTest {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = new FhirContext();
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionTest.class);
|
||||
private static int ourPort;
|
||||
private static boolean ourReturnOperationOutcome;
|
||||
|
||||
private static Server ourServer;
|
||||
private static FhirContext ourCtx = new FhirContext();
|
||||
|
||||
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourReturnOperationOutcome = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransaction() throws Exception {
|
||||
|
@ -95,6 +107,65 @@ public class TransactionTest {
|
|||
assertEquals(nowInstant.getValueAsString(), entry2.getDeletedAt().getValueAsString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionWithOperationOutcome() throws Exception {
|
||||
ourReturnOperationOutcome = true;
|
||||
|
||||
Bundle b = new Bundle();
|
||||
InstantDt nowInstant = InstantDt.withCurrentTime();
|
||||
|
||||
Patient p1 = new Patient();
|
||||
p1.addName().addFamily("Family1");
|
||||
BundleEntry entry = b.addEntry();
|
||||
entry.getId().setValue("1");
|
||||
entry.setResource(p1);
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addName().addFamily("Family2");
|
||||
entry = b.addEntry();
|
||||
entry.getId().setValue("2");
|
||||
entry.setResource(p2);
|
||||
|
||||
BundleEntry deletedEntry = b.addEntry();
|
||||
deletedEntry.setId(new IdDt("Patient/3"));
|
||||
deletedEntry.setDeleted(nowInstant);
|
||||
|
||||
String bundleString = ourCtx.newXmlParser().setPrettyPrint(true).encodeBundleToString(b);
|
||||
ourLog.info(bundleString);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/");
|
||||
httpPost.addHeader("Accept", Constants.CT_ATOM_XML + "; pretty=true");
|
||||
httpPost.setEntity(new StringEntity(bundleString, ContentType.create(Constants.CT_ATOM_XML, "UTF-8")));
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent()); IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
ourLog.info(responseContent);
|
||||
|
||||
Bundle bundle = new FhirContext().newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(4, bundle.size());
|
||||
|
||||
assertEquals(OperationOutcome.class, bundle.getEntries().get(0).getResource().getClass());
|
||||
assertEquals("OperationOutcome (no ID)", bundle.getEntries().get(0).getTitle().getValue());
|
||||
|
||||
BundleEntry entry0 = bundle.getEntries().get(1);
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/81", entry0.getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/81/_history/91", entry0.getLinkSelf().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/1", entry0.getLinkAlternate().getValue());
|
||||
|
||||
BundleEntry entry1 = bundle.getEntries().get(2);
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/82", entry1.getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/82/_history/92", entry1.getLinkSelf().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/2", entry1.getLinkAlternate().getValue());
|
||||
|
||||
BundleEntry entry2 = bundle.getEntries().get(3);
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/3", entry2.getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/3/_history/93", entry2.getLinkSelf().getValue());
|
||||
assertEquals(nowInstant.getValueAsString(), entry2.getDeletedAt().getValueAsString());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
|
@ -142,7 +213,17 @@ public class TransactionTest {
|
|||
next.setId(new IdDt("Patient", newId, "9"+Integer.toString(index)));
|
||||
index++;
|
||||
}
|
||||
return theResources;
|
||||
|
||||
List<IResource> retVal = theResources;
|
||||
if (ourReturnOperationOutcome) {
|
||||
retVal = new ArrayList<IResource>();
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().setDetails("AAAAA");
|
||||
retVal.add(oo);
|
||||
retVal.addAll(theResources);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -73,6 +72,31 @@ public class UpdateTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateWhichReturnsCreate() throws Exception {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setValue("002");
|
||||
|
||||
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/001CREATE");
|
||||
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
|
||||
HttpResponse status = ourClient.execute(httpPost);
|
||||
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
|
||||
OperationOutcome oo = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, responseContent);
|
||||
assertEquals("OODETAILS", oo.getIssueFirstRep().getDetails().getValue());
|
||||
|
||||
assertEquals(201, status.getStatusLine().getStatusCode());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/001CREATE/_history/002", status.getFirstHeader("location").getValue());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMethodReturnsInvalidId() throws Exception {
|
||||
|
||||
|
@ -346,6 +370,10 @@ public class UpdateTest {
|
|||
IdDt id = theId.withVersion(thePatient.getIdentifierFirstRep().getValue().getValue());
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().setDetails("OODETAILS");
|
||||
if (theId.getValueAsString().contains("CREATE")) {
|
||||
return new MethodOutcome(id,oo, true);
|
||||
}
|
||||
|
||||
return new MethodOutcome(id,oo);
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ import ca.uhn.fhir.rest.server.IBundleProvider;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
@ -468,19 +469,12 @@ public abstract class BaseFhirDao implements IDao {
|
|||
nextValue = newValue;
|
||||
|
||||
/*
|
||||
@SuppressWarnings("unchecked")
|
||||
PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>>) UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null);
|
||||
if (unit.isCompatible(UCUM.DAY)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit = (PhysicsUnit<Time>) unit;
|
||||
UnitConverter conv = timeUnit.getConverterTo(UCUM.DAY);
|
||||
double dayValue = conv.convert(nextValue.getValue().getValue().doubleValue());
|
||||
DurationDt newValue = new DurationDt();
|
||||
newValue.setSystem(UCUM_NS);
|
||||
newValue.setCode(UCUM.DAY.getSymbol());
|
||||
newValue.setValue(dayValue);
|
||||
nextValue=newValue;
|
||||
}
|
||||
* @SuppressWarnings("unchecked") PhysicsUnit<? extends org.unitsofmeasurement.quantity.Quantity<?>> unit = (PhysicsUnit<? extends
|
||||
* org.unitsofmeasurement.quantity.Quantity<?>>) UCUMFormat.getCaseInsensitiveInstance().parse(nextValue.getCode().getValue(), null); if (unit.isCompatible(UCUM.DAY)) {
|
||||
*
|
||||
* @SuppressWarnings("unchecked") PhysicsUnit<org.unitsofmeasurement.quantity.Time> timeUnit = (PhysicsUnit<Time>) unit; UnitConverter conv =
|
||||
* timeUnit.getConverterTo(UCUM.DAY); double dayValue = conv.convert(nextValue.getValue().getValue().doubleValue()); DurationDt newValue = new DurationDt();
|
||||
* newValue.setSystem(UCUM_NS); newValue.setCode(UCUM.DAY.getSymbol()); newValue.setValue(dayValue); nextValue=newValue; }
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -512,7 +506,6 @@ public abstract class BaseFhirDao implements IDao {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
protected List<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) {
|
||||
ArrayList<ResourceIndexedSearchParamQuantity> retVal = new ArrayList<ResourceIndexedSearchParamQuantity>();
|
||||
|
||||
|
@ -542,7 +535,8 @@ public abstract class BaseFhirDao implements IDao {
|
|||
continue;
|
||||
}
|
||||
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(), nextValue.getUnits().getValue());
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValue.getValue().getValue(), nextValue.getSystem().getValueAsString(),
|
||||
nextValue.getUnits().getValue());
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
} else {
|
||||
|
@ -560,7 +554,6 @@ public abstract class BaseFhirDao implements IDao {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
protected List<ResourceIndexedSearchParamString> extractSearchParamStrings(ResourceTable theEntity, IResource theResource) {
|
||||
ArrayList<ResourceIndexedSearchParamString> retVal = new ArrayList<ResourceIndexedSearchParamString>();
|
||||
|
||||
|
@ -631,7 +624,8 @@ public abstract class BaseFhirDao implements IDao {
|
|||
} else if (nextObject instanceof ContactDt) {
|
||||
ContactDt nextContact = (ContactDt) nextObject;
|
||||
if (nextContact.getValue().isEmpty() == false) {
|
||||
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextContact.getValue().getValueAsString()), nextContact.getValue().getValueAsString());
|
||||
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(resourceName, normalizeString(nextContact.getValue().getValueAsString()), nextContact
|
||||
.getValue().getValueAsString());
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
|
@ -690,7 +684,8 @@ public abstract class BaseFhirDao implements IDao {
|
|||
} else if (nextObject instanceof CodeableConceptDt) {
|
||||
CodeableConceptDt nextCC = (CodeableConceptDt) nextObject;
|
||||
if (!nextCC.getText().isEmpty()) {
|
||||
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), normalizeString(nextCC.getText().getValue()), nextCC.getText().getValue());
|
||||
ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(nextSpDef.getName(), normalizeString(nextCC.getText().getValue()), nextCC.getText()
|
||||
.getValue());
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
|
@ -1105,6 +1100,14 @@ public abstract class BaseFhirDao implements IDao {
|
|||
entity.setPublished(new Date());
|
||||
}
|
||||
|
||||
if (theResource != null) {
|
||||
String resourceType = myContext.getResourceDefinition(theResource).getName();
|
||||
if (isNotBlank(entity.getResourceType()) && !entity.getResourceType().equals(resourceType)) {
|
||||
throw new UnprocessableEntityException("Existing resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + entity.getResourceType() + "] - Cannot update with ["
|
||||
+ resourceType + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if (theUpdateHistory) {
|
||||
final ResourceHistoryTable historyEntry = entity.toHistory();
|
||||
myEntityManager.persist(historyEntry);
|
||||
|
@ -1138,7 +1141,6 @@ public abstract class BaseFhirDao implements IDao {
|
|||
|
||||
} else {
|
||||
|
||||
|
||||
stringParams = extractSearchParamStrings(entity, theResource);
|
||||
numberParams = extractSearchParamNumber(entity, theResource);
|
||||
quantityParams = extractSearchParamQuantity(entity, theResource);
|
||||
|
@ -1253,6 +1255,4 @@ public abstract class BaseFhirDao implements IDao {
|
|||
return InstantDt.withCurrentTime();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -357,7 +357,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
|
|||
if (entity == null) {
|
||||
if (theId.hasVersionIdPart()) {
|
||||
TypedQuery<ResourceHistoryTable> 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", theId.getIdPartAsLong());
|
||||
q.setParameter("RID", pid);
|
||||
q.setParameter("RTYP", myResourceName);
|
||||
q.setParameter("RVER", theId.getVersionIdPartAsLong());
|
||||
entity = q.getSingleResult();
|
||||
|
@ -367,11 +367,24 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
|
|||
}
|
||||
}
|
||||
|
||||
validateGivenIdIsAppropriateToRetrieveResource(theId, entity);
|
||||
|
||||
validateResourceType(entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void validateGivenIdIsAppropriateToRetrieveResource(IdDt theId, BaseHasResource entity) {
|
||||
if (entity.getForcedId() != null) {
|
||||
if (theId.isIdPartValidLong()) {
|
||||
// This means that the resource with the given numeric ID exists, but it has a "forced ID", meaning that
|
||||
// as far as the outside world is concerned, the given ID doesn't exist (it's just an internal pointer to the
|
||||
// forced ID)
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTag(IdDt theId, String theScheme, String theTerm) {
|
||||
StopWatch w = new StopWatch();
|
||||
|
@ -1433,6 +1446,7 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
|
|||
if (entity == null) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
validateGivenIdIsAppropriateToRetrieveResource(theId, entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import ca.uhn.fhir.jpa.util.StopWatch;
|
|||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
|
@ -35,7 +37,7 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
|
|||
|
||||
@Transactional(propagation = Propagation.REQUIRED)
|
||||
@Override
|
||||
public void transaction(List<IResource> theResources) {
|
||||
public List<IResource> transaction(List<IResource> theResources) {
|
||||
ourLog.info("Beginning transaction with {} resources", theResources.size());
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
|
@ -150,7 +152,16 @@ public class FhirSystemDao extends BaseFhirDao implements IFhirSystemDao {
|
|||
long delay = System.currentTimeMillis() - start;
|
||||
ourLog.info("Transaction completed in {}ms with {} creations and {} updates", new Object[] { delay, creations, updates });
|
||||
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Transaction completed in "+delay+"ms with "+creations+" creations and "+updates+" updates");
|
||||
|
||||
ArrayList<IResource> retVal = new ArrayList<IResource>();
|
||||
retVal.add(oo);
|
||||
retVal.addAll(theResources);
|
||||
|
||||
notifyWriteCompleted();
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -10,7 +10,7 @@ import ca.uhn.fhir.rest.server.IBundleProvider;
|
|||
|
||||
public interface IFhirSystemDao extends IDao {
|
||||
|
||||
void transaction(List<IResource> theResources);
|
||||
List<IResource> transaction(List<IResource> theResources);
|
||||
|
||||
IBundleProvider history(Date theDate);
|
||||
|
||||
|
|
|
@ -27,9 +27,12 @@ import ca.uhn.fhir.rest.annotation.Validate;
|
|||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
|
||||
public class JpaResourceProvider<T extends IResource> extends BaseJpaProvider implements IResourceProvider {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaResourceProvider.class);
|
||||
|
||||
@Autowired(required = true)
|
||||
private FhirContext myContext;
|
||||
|
||||
|
@ -140,6 +143,12 @@ public class JpaResourceProvider<T extends IResource> extends BaseJpaProvider im
|
|||
startRequest(theRequest);
|
||||
try {
|
||||
return myDao.update(theResource, theId);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
|
||||
theResource.setId(theId);
|
||||
MethodOutcome retVal = myDao.create(theResource);
|
||||
retVal.setCreated(true);
|
||||
return retVal;
|
||||
} finally {
|
||||
endRequest(theRequest);
|
||||
}
|
||||
|
|
|
@ -38,8 +38,7 @@ public class JpaSystemProvider extends BaseJpaProvider {
|
|||
public List<IResource> transaction(HttpServletRequest theRequest, @TransactionParam List<IResource> theResources) {
|
||||
startRequest(theRequest);
|
||||
try {
|
||||
myDao.transaction(theResources);
|
||||
return theResources;
|
||||
return myDao.transaction(theResources);
|
||||
} finally {
|
||||
endRequest(theRequest);
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ import ca.uhn.fhir.rest.server.IBundleProvider;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
||||
public class FhirResourceDaoTest {
|
||||
|
||||
|
@ -973,7 +974,8 @@ public class FhirResourceDaoTest {
|
|||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("urn:system").setValue("testSearchTokenParam001");
|
||||
patient.addName().addFamily("Tester").addGiven("testSearchTokenParam1");
|
||||
patient.addCommunication().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem").setDisplay("testSearchTokenParamDisplay");
|
||||
patient.addCommunication().setText("testSearchTokenParamComText").addCoding().setCode("testSearchTokenParamCode").setSystem("testSearchTokenParamSystem")
|
||||
.setDisplay("testSearchTokenParamDisplay");
|
||||
ourPatientDao.create(patient);
|
||||
|
||||
patient = new Patient();
|
||||
|
@ -1327,6 +1329,86 @@ public class FhirResourceDaoTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier("urn:system", "testUpdateRejectsInvalidTypes");
|
||||
p1.addName().addFamily("Tester").addGiven("testUpdateRejectsInvalidTypes");
|
||||
IdDt p1id = ourPatientDao.create(p1).getId();
|
||||
|
||||
Organization p2 = new Organization();
|
||||
p2.getName().setValue("testUpdateRejectsInvalidTypes");
|
||||
try {
|
||||
ourOrganizationDao.update(p2, new IdDt("Organization/" + p1id.getIdPart()));
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
ourOrganizationDao.update(p2, new IdDt("Patient/" + p1id.getIdPart()));
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRejectsIdWhichPointsToForcedId() throws InterruptedException {
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier("urn:system", "testUpdateRejectsIdWhichPointsToForcedId01");
|
||||
p1.addName().addFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId01");
|
||||
p1.setId("ABABA");
|
||||
IdDt p1id = ourPatientDao.create(p1).getId();
|
||||
assertEquals("ABABA", p1id.getIdPart());
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addIdentifier("urn:system", "testUpdateRejectsIdWhichPointsToForcedId02");
|
||||
p2.addName().addFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId02");
|
||||
IdDt p2id = ourPatientDao.create(p2).getId();
|
||||
long p1longId = p2id.getIdPartAsLong() - 1;
|
||||
|
||||
try {
|
||||
ourPatientDao.read(new IdDt("Patient/" + p1longId));
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
ourPatientDao.update(p1, new IdDt("Patient/" + p1longId));
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testReadForcedIdVersionHistory() throws InterruptedException {
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier("urn:system", "testReadVorcedIdVersionHistory01");
|
||||
p1.setId("testReadVorcedIdVersionHistory");
|
||||
IdDt p1id = ourPatientDao.create(p1).getId();
|
||||
assertEquals("testReadVorcedIdVersionHistory", p1id.getIdPart());
|
||||
|
||||
p1.addIdentifier("urn:system", "testReadVorcedIdVersionHistory02");
|
||||
IdDt p1idv2 = ourPatientDao.update(p1, p1id).getId();
|
||||
assertEquals("testReadVorcedIdVersionHistory", p1idv2.getIdPart());
|
||||
|
||||
assertNotEquals(p1id.getValue(), p1idv2.getValue());
|
||||
|
||||
Patient v1 = ourPatientDao.read(p1id);
|
||||
assertEquals(1, v1.getIdentifier().size());
|
||||
|
||||
Patient v2 = ourPatientDao.read(p1idv2);
|
||||
assertEquals(2, v2.getIdentifier().size());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends IResource> List<T> toList(IBundleProvider theSearch) {
|
||||
return (List<T>) theSearch.getResources(0, theSearch.size());
|
||||
|
|
|
@ -33,6 +33,7 @@ import ca.uhn.fhir.model.dstu.valueset.EncounterStateEnum;
|
|||
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.gclient.StringClientParam;
|
||||
|
@ -82,6 +83,24 @@ public class CompleteResourceProviderTest {
|
|||
|
||||
private static IFhirResourceDao<Questionnaire> questionnaireDao;
|
||||
|
||||
@Test
|
||||
public void testUpdateWithClientSuppliedIdWhichDoesntExist() {
|
||||
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExist");
|
||||
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier().setSystem("urn:system").setValue("testUpdateWithClientSuppliedIdWhichDoesntExist");
|
||||
MethodOutcome outcome = ourClient.update().resource(p1).withId("testUpdateWithClientSuppliedIdWhichDoesntExist").execute();
|
||||
assertEquals(true, outcome.getCreated().booleanValue());
|
||||
IdDt p1Id = outcome.getId();
|
||||
|
||||
assertThat(p1Id.getValue(), containsString("Patient/testUpdateWithClientSuppliedIdWhichDoesntExist/_history"));
|
||||
|
||||
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExist")).encodedJson().prettyPrint().execute();
|
||||
assertEquals(1, actual.size());
|
||||
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithClientSuppliedId() {
|
||||
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testCreateWithId01");
|
||||
|
@ -189,6 +208,34 @@ public class CompleteResourceProviderTest {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
|
||||
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateRejectsInvalidTypes");
|
||||
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier("urn:system", "testUpdateRejectsInvalidTypes");
|
||||
p1.addName().addFamily("Tester").addGiven("testUpdateRejectsInvalidTypes");
|
||||
IdDt p1id = ourClient.create().resource(p1).execute().getId();
|
||||
|
||||
Organization p2 = new Organization();
|
||||
p2.getName().setValue("testUpdateRejectsInvalidTypes");
|
||||
try {
|
||||
ourClient.update().resource(p2).withId("Organization/" + p1id.getIdPart()).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
ourClient.update().resource(p2).withId("Patient/" + p1id.getIdPart()).execute();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeepChaining() {
|
||||
delete("Location", Location.SP_NAME, "testDeepChainingL1");
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-tester-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value></param-value>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-base"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-jpaserver-base"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-jpaserver-test"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-base"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-jpaserver-base"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-jpaserver-test"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
|
|
@ -129,6 +129,11 @@
|
|||
<artifactId>spring-context-support</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${spring_version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
|
@ -167,8 +172,6 @@
|
|||
<version>${jetty_version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-tester-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value></param-value>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
|
|
Loading…
Reference in New Issue