mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-08 05:58:27 +00:00
Add error checking and better handling for match URLs in JPA server
This commit is contained in:
parent
541e6cdcb1
commit
119a4f36d9
@ -6,7 +6,7 @@
|
|||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
<classpathentry kind="src" output="target/classes" path="src/main/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
<classpathentry kind="src" output="target/test-classes" path="src/test/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
@ -15,26 +15,6 @@
|
|||||||
<arguments>
|
<arguments>
|
||||||
</arguments>
|
</arguments>
|
||||||
</buildCommand>
|
</buildCommand>
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
|
||||||
<triggers>full,incremental,</triggers>
|
|
||||||
<arguments>
|
|
||||||
<dictionary>
|
|
||||||
<key>LaunchConfigHandle</key>
|
|
||||||
<value><project>/.externalToolBuilders/org.eclipse.wst.validation.validationbuilder.launch</value>
|
|
||||||
</dictionary>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
|
||||||
<triggers>full,incremental,</triggers>
|
|
||||||
<arguments>
|
|
||||||
<dictionary>
|
|
||||||
<key>LaunchConfigHandle</key>
|
|
||||||
<value><project>/.externalToolBuilders/org.eclipse.m2e.core.maven2Builder (1).launch</value>
|
|
||||||
</dictionary>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
</buildSpec>
|
||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||||
|
@ -7,15 +7,15 @@
|
|||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
|
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
|
||||||
<classpathentry kind="src" path="src/main/java">
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" path="src/main/resources"/>
|
<classpathentry kind="src" output="target/classes" path="src/main/resources"/>
|
||||||
<classpathentry kind="src" path="target/generated-sources/tinder"/>
|
<classpathentry kind="src" output="target/classes" path="target/generated-sources/tinder"/>
|
||||||
<classpathentry kind="src" path="target/generated-resources/tinder">
|
<classpathentry kind="src" output="target/classes" path="target/generated-resources/tinder">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
@ -16,26 +16,6 @@
|
|||||||
<arguments>
|
<arguments>
|
||||||
</arguments>
|
</arguments>
|
||||||
</buildCommand>
|
</buildCommand>
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
|
||||||
<triggers>full,incremental,</triggers>
|
|
||||||
<arguments>
|
|
||||||
<dictionary>
|
|
||||||
<key>LaunchConfigHandle</key>
|
|
||||||
<value><project>/.externalToolBuilders/org.eclipse.wst.validation.validationbuilder (1).launch</value>
|
|
||||||
</dictionary>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
|
|
||||||
<triggers>full,incremental,</triggers>
|
|
||||||
<arguments>
|
|
||||||
<dictionary>
|
|
||||||
<key>LaunchConfigHandle</key>
|
|
||||||
<value><project>/.externalToolBuilders/org.eclipse.m2e.core.maven2Builder (2).launch</value>
|
|
||||||
</dictionary>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
</buildSpec>
|
</buildSpec>
|
||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
|
||||||
|
@ -174,8 +174,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
|
|
||||||
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
|
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
|
||||||
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
|
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
|
||||||
|
|
||||||
|
|
||||||
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
|
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -193,7 +192,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
String[] nextPathsSplit = nextPathsUnsplit.split("\\|");
|
String[] nextPathsSplit = nextPathsUnsplit.split("\\|");
|
||||||
for (String nextPath : nextPathsSplit) {
|
for (String nextPath : nextPathsSplit) {
|
||||||
nextPath = nextPath.trim();
|
nextPath = nextPath.trim();
|
||||||
|
|
||||||
List<Class<? extends IBaseResource>> allowedTypesInField = null;
|
List<Class<? extends IBaseResource>> allowedTypesInField = null;
|
||||||
for (Object nextObject : extractValues(nextPath, theResource)) {
|
for (Object nextObject : extractValues(nextPath, theResource)) {
|
||||||
if (nextObject == null) {
|
if (nextObject == null) {
|
||||||
@ -219,7 +218,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
try {
|
try {
|
||||||
resourceDefinition = getContext().getResourceDefinition(typeString);
|
resourceDefinition = getContext().getResourceDefinition(typeString);
|
||||||
} catch (DataFormatException e) {
|
} catch (DataFormatException e) {
|
||||||
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextValue.getReference().getValue());
|
throw new InvalidRequestException(
|
||||||
|
"Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextValue.getReference().getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
Class<? extends IBaseResource> type = resourceDefinition.getImplementingClass();
|
||||||
@ -253,15 +253,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
String resName = targetResourceDef.getName();
|
String resName = targetResourceDef.getName();
|
||||||
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!typeString.equals(target.getResourceType())) {
|
if (!typeString.equals(target.getResourceType())) {
|
||||||
throw new UnprocessableEntityException("Resource contains reference to " + nextValue.getReference().getValue() + " but resource with ID " + nextValue.getReference().getIdPart() + " is actually of type " + target.getResourceType());
|
throw new UnprocessableEntityException("Resource contains reference to " + nextValue.getReference().getValue() + " but resource with ID " + nextValue.getReference().getIdPart()
|
||||||
|
+ " is actually of type " + target.getResourceType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Is the target type an allowable type of resource for the path where it is referenced?
|
* Is the target type an allowable type of resource for the path where it is referenced?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (allowedTypesInField == null) {
|
if (allowedTypesInField == null) {
|
||||||
BaseRuntimeChildDefinition childDef = getContext().newTerser().getDefinition(theResource.getClass(), nextPath);
|
BaseRuntimeChildDefinition childDef = getContext().newTerser().getDefinition(theResource.getClass(), nextPath);
|
||||||
if (childDef instanceof RuntimeChildResourceDefinition) {
|
if (childDef instanceof RuntimeChildResourceDefinition) {
|
||||||
@ -272,19 +273,19 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
allowedTypesInField.add(IBaseResource.class);
|
allowedTypesInField.add(IBaseResource.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean acceptableLink = false;
|
boolean acceptableLink = false;
|
||||||
for(Class<? extends IBaseResource> next : allowedTypesInField) {
|
for (Class<? extends IBaseResource> next : allowedTypesInField) {
|
||||||
if (next.isAssignableFrom(targetResourceDef.getImplementingClass())) {
|
if (next.isAssignableFrom(targetResourceDef.getImplementingClass())) {
|
||||||
acceptableLink = true;
|
acceptableLink = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!acceptableLink) {
|
if (!acceptableLink) {
|
||||||
throw new UnprocessableEntityException("Invalid reference found at path '" + nextPath + "'. Resource type '" + targetResourceDef.getName() + "' is not valid for this path");
|
throw new UnprocessableEntityException("Invalid reference found at path '" + nextPath + "'. Resource type '" + targetResourceDef.getName() + "' is not valid for this path");
|
||||||
}
|
}
|
||||||
|
|
||||||
nextEntity = new ResourceLink(nextPath, theEntity, target);
|
nextEntity = new ResourceLink(nextPath, theEntity, target);
|
||||||
} else {
|
} else {
|
||||||
if (!multiType) {
|
if (!multiType) {
|
||||||
@ -683,11 +684,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called when an update to an existing resource detects that the resource supplied for update is
|
* This method is called when an update to an existing resource detects that the resource supplied for update is missing a tag/profile/security label that the currently persisted resource holds.
|
||||||
* missing a tag/profile/security label that the currently persisted resource holds.
|
|
||||||
* <p>
|
* <p>
|
||||||
* The default implementation removes any profile declarations, but leaves tags and security labels in place.
|
* The default implementation removes any profile declarations, but leaves tags and security labels in place. Subclasses may choose to override and change this behaviour.
|
||||||
* Subclasses may choose to override and change this behaviour.
|
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theEntity
|
* @param theEntity
|
||||||
@ -695,8 +694,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
* @param theTag
|
* @param theTag
|
||||||
* The tag
|
* The tag
|
||||||
* @return Retturns <code>true</code> if the tag should be removed
|
* @return Retturns <code>true</code> if the tag should be removed
|
||||||
* @see <a href="http://hl7.org/fhir/2015Sep/resource.html#1.11.3.7">Updates to Tags, Profiles, and Security
|
* @see <a href="http://hl7.org/fhir/2015Sep/resource.html#1.11.3.7">Updates to Tags, Profiles, and Security Labels</a> for a description of the logic that the default behaviour folows.
|
||||||
* Labels</a> for a description of the logic that the default behaviour folows.
|
|
||||||
*/
|
*/
|
||||||
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
|
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
|
||||||
if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
|
if (theTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
|
||||||
@ -709,6 +707,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
|
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
|
||||||
|
|
||||||
SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef);
|
SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef);
|
||||||
|
|
||||||
|
if (paramMap.isEmpty()) {
|
||||||
|
throw new InvalidRequestException("Invalid match URL[" + theMatchUrl + "] - URL has no search parameters");
|
||||||
|
}
|
||||||
|
|
||||||
IFhirResourceDao<R> dao = getDao(theResourceType);
|
IFhirResourceDao<R> dao = getDao(theResourceType);
|
||||||
Set<Long> ids = dao.searchForIdsWithAndOr(paramMap);
|
Set<Long> ids = dao.searchForIdsWithAndOr(paramMap);
|
||||||
@ -719,23 +721,28 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) {
|
public static SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition resourceDef) {
|
||||||
SearchParameterMap paramMap = new SearchParameterMap();
|
SearchParameterMap paramMap = new SearchParameterMap();
|
||||||
List<NameValuePair> parameters;
|
List<NameValuePair> parameters;
|
||||||
try {
|
String matchUrl = theMatchUrl;
|
||||||
String matchUrl = theMatchUrl;
|
int questionMarkIndex = matchUrl.indexOf('?');
|
||||||
if (matchUrl.indexOf('?') == -1) {
|
if (questionMarkIndex != -1) {
|
||||||
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: URL does not contain any parameters ('?' not detected)");
|
matchUrl = matchUrl.substring(questionMarkIndex + 1);
|
||||||
}
|
|
||||||
matchUrl = matchUrl.replace("|", "%7C");
|
|
||||||
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
|
|
||||||
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
|
|
||||||
matchUrl = matchUrl.replace("=>", "=%3E");
|
|
||||||
matchUrl = matchUrl.replace("=<", "=%3C");
|
|
||||||
parameters = URLEncodedUtils.parse(new URI(matchUrl), "UTF-8");
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Error was: " + e.toString());
|
|
||||||
}
|
}
|
||||||
|
matchUrl = matchUrl.replace("|", "%7C");
|
||||||
|
matchUrl = matchUrl.replace("=>=", "=%3E%3D");
|
||||||
|
matchUrl = matchUrl.replace("=<=", "=%3C%3D");
|
||||||
|
matchUrl = matchUrl.replace("=>", "=%3E");
|
||||||
|
matchUrl = matchUrl.replace("=<", "=%3C");
|
||||||
|
if (matchUrl.contains(" ")) {
|
||||||
|
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - URL is invalid (must not contain spaces)");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters = URLEncodedUtils.parse((matchUrl), Constants.CHARSET_UTF8, '&');
|
||||||
|
|
||||||
ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
|
ArrayListMultimap<String, QualifiedParamList> nameToParamLists = ArrayListMultimap.create();
|
||||||
for (NameValuePair next : parameters) {
|
for (NameValuePair next : parameters) {
|
||||||
|
if (isBlank(next.getValue())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String paramName = next.getName();
|
String paramName = next.getName();
|
||||||
String qualifier = null;
|
String qualifier = null;
|
||||||
for (int i = 0; i < paramMap.size(); i++) {
|
for (int i = 0; i < paramMap.size(); i++) {
|
||||||
@ -779,11 +786,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextParamName.startsWith("_")) {
|
if (nextParamName.startsWith("_")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName);
|
RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName);
|
||||||
if (paramDef == null) {
|
if (paramDef == null) {
|
||||||
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
|
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
|
||||||
@ -1030,8 +1037,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
}
|
}
|
||||||
} else if (theForHistoryOperation) {
|
} else if (theForHistoryOperation) {
|
||||||
/*
|
/*
|
||||||
* If the create and update times match, this was when the resource was created
|
* If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
|
||||||
* so we should mark it as a POST. Otherwise, it's a PUT.
|
|
||||||
*/
|
*/
|
||||||
Date published = theEntity.getPublished().getValue();
|
Date published = theEntity.getPublished().getValue();
|
||||||
Date updated = theEntity.getUpdated().getValue();
|
Date updated = theEntity.getUpdated().getValue();
|
||||||
@ -1041,7 +1047,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
|
ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setId(theEntity.getIdDt());
|
res.setId(theEntity.getIdDt());
|
||||||
|
|
||||||
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
|
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
|
||||||
@ -1125,8 +1131,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
|
protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing,
|
||||||
|
boolean theUpdateVersion, Date theUpdateTime) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This should be the very first thing..
|
* This should be the very first thing..
|
||||||
*/
|
*/
|
||||||
@ -1134,14 +1141,15 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
validateResourceForStorage((T) theResource, theEntity);
|
validateResourceForStorage((T) theResource, theEntity);
|
||||||
String resourceType = myContext.getResourceDefinition(theResource).getName();
|
String resourceType = myContext.getResourceDefinition(theResource).getName();
|
||||||
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
|
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
|
||||||
throw new UnprocessableEntityException("Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
|
throw new UnprocessableEntityException(
|
||||||
|
"Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theEntity.getPublished() == null) {
|
if (theEntity.getPublished() == null) {
|
||||||
theEntity.setPublished(theUpdateTime);
|
theEntity.setPublished(theUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theUpdateHistory) {
|
if (theUpdateHistory) {
|
||||||
final ResourceHistoryTable historyEntry = theEntity.toHistory();
|
final ResourceHistoryTable historyEntry = theEntity.toHistory();
|
||||||
myEntityManager.persist(historyEntry);
|
myEntityManager.persist(historyEntry);
|
||||||
@ -1238,7 +1246,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
theEntity.setResourceLinks(links);
|
theEntity.setResourceLinks(links);
|
||||||
theEntity.setHasLinks(links.isEmpty() == false);
|
theEntity.setHasLinks(links.isEmpty() == false);
|
||||||
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
|
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
populateResourceIntoEntity(theResource, theEntity);
|
populateResourceIntoEntity(theResource, theEntity);
|
||||||
@ -1261,7 +1269,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
theEntity = myEntityManager.merge(theEntity);
|
theEntity = myEntityManager.merge(theEntity);
|
||||||
|
|
||||||
postUpdate(theEntity, (T) theResource);
|
postUpdate(theEntity, (T) theResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1354,37 +1362,37 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
|
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time.
|
||||||
* first time.
|
|
||||||
*
|
*
|
||||||
* @param theEntity
|
* @param theEntity
|
||||||
* The resource
|
* The resource
|
||||||
* @param theResource The resource being persisted
|
* @param theResource
|
||||||
|
* The resource being persisted
|
||||||
*/
|
*/
|
||||||
protected void postUpdate(ResourceTable theEntity, T theResource) {
|
protected void postUpdate(ResourceTable theEntity, T theResource) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
|
* Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the first time.
|
||||||
* first time.
|
|
||||||
*
|
*
|
||||||
* @param theEntity
|
* @param theEntity
|
||||||
* The resource
|
* The resource
|
||||||
* @param theResource The resource being persisted
|
* @param theResource
|
||||||
|
* The resource being persisted
|
||||||
*/
|
*/
|
||||||
protected void postPersist(ResourceTable theEntity, T theResource) {
|
protected void postPersist(ResourceTable theEntity, T theResource) {
|
||||||
// nothing
|
// nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked immediately before storing a new resource, or an update to an existing resource to allow
|
* This method is invoked immediately before storing a new resource, or an update to an existing resource to allow the DAO to ensure that it is valid for persistence. By default, checks for the
|
||||||
* the DAO to ensure that it is valid for persistence. By default, checks for the "subsetted" tag and rejects
|
* "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
|
||||||
* resources which have it. Subclasses should call the superclass implementation to preserve this check.
|
|
||||||
*
|
*
|
||||||
* @param theResource
|
* @param theResource
|
||||||
* The resource that is about to be persisted
|
* The resource that is about to be persisted
|
||||||
* @param theEntityToSave TODO
|
* @param theEntityToSave
|
||||||
|
* TODO
|
||||||
*/
|
*/
|
||||||
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
|
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
|
||||||
IResource res = (IResource) theResource;
|
IResource res = (IResource) theResource;
|
||||||
|
@ -56,6 +56,7 @@ import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
|
|||||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||||
import ca.uhn.fhir.rest.api.SortSpec;
|
import ca.uhn.fhir.rest.api.SortSpec;
|
||||||
@ -129,7 +130,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||||||
ourLog.trace("Skipping search for subscription");
|
ourLog.trace("Skipping search for subscription");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ourLog.info("Subscription search from {} to {}", start, end);
|
|
||||||
|
ourLog.debug("Subscription {} search from {} to {}", new Object[] { subscription.getId().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end)) });
|
||||||
|
|
||||||
DateRangeParam range = new DateRangeParam();
|
DateRangeParam range = new DateRangeParam();
|
||||||
range.setLowerBound(new DateParam(QuantityCompararatorEnum.GREATERTHAN, start));
|
range.setLowerBound(new DateParam(QuantityCompararatorEnum.GREATERTHAN, start));
|
||||||
@ -176,7 +178,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ResourceTable updateEntity(IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
|
protected ResourceTable updateEntity(IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion,
|
||||||
|
Date theUpdateTime) {
|
||||||
ResourceTable retVal = super.updateEntity(theResource, theEntity, theUpdateHistory, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime);
|
ResourceTable retVal = super.updateEntity(theResource, theEntity, theUpdateHistory, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime);
|
||||||
|
|
||||||
Subscription resource = (Subscription) theResource;
|
Subscription resource = (Subscription) theResource;
|
||||||
@ -278,16 +281,17 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||||||
@Override
|
@Override
|
||||||
public void purgeInactiveSubscriptions() {
|
public void purgeInactiveSubscriptions() {
|
||||||
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
|
Long purgeInactiveAfterMillis = getConfig().getSubscriptionPurgeInactiveAfterMillis();
|
||||||
if (getConfig().isSubscriptionEnabled()==false || purgeInactiveAfterMillis == null) {
|
if (getConfig().isSubscriptionEnabled() == false || purgeInactiveAfterMillis == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
|
Date cutoff = new Date(System.currentTimeMillis() - purgeInactiveAfterMillis);
|
||||||
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
|
Collection<SubscriptionTable> toPurge = mySubscriptionTableDao.findInactiveBeforeCutoff(cutoff);
|
||||||
for (SubscriptionTable subscriptionTable : toPurge) {
|
for (SubscriptionTable subscriptionTable : toPurge) {
|
||||||
|
|
||||||
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
|
final IdDt subscriptionId = subscriptionTable.getSubscriptionResource().getIdDt();
|
||||||
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}", new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
|
ourLog.info("Deleting inactive subscription {} - Created {}, last client poll {}",
|
||||||
|
new Object[] { subscriptionId.toUnqualified(), subscriptionTable.getCreated(), subscriptionTable.getLastClientPoll() });
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||||
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
|
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
|
||||||
txTemplate.execute(new TransactionCallback<Void>() {
|
txTemplate.execute(new TransactionCallback<Void>() {
|
||||||
|
@ -1,19 +1,32 @@
|
|||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.dao;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||||
|
|
||||||
public class BaseJpaTest {
|
public class BaseJpaTest {
|
||||||
|
|
||||||
|
public static String loadClasspath(String resource) throws IOException {
|
||||||
|
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream(resource);
|
||||||
|
if (bundleRes == null) {
|
||||||
|
throw new NullPointerException("Can not load " + resource);
|
||||||
|
}
|
||||||
|
String bundleStr = IOUtils.toString(bundleRes);
|
||||||
|
return bundleStr;
|
||||||
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassShutdownDerby() throws SQLException {
|
public static void afterClassShutdownDerby() throws SQLException {
|
||||||
// try {
|
// try {
|
||||||
|
@ -18,7 +18,6 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
@ -28,7 +27,6 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.provider.SystemProviderDstu2Test;
|
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
import ca.uhn.fhir.model.api.BundleEntry;
|
import ca.uhn.fhir.model.api.BundleEntry;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
@ -346,8 +344,8 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testTransactionWithCidIds2() throws Exception {
|
public void testTransactionWithCidIds2() throws Exception {
|
||||||
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/dstu1_bundle.xml");
|
String resource = "/dstu1_bundle.xml";
|
||||||
String bundleStr = IOUtils.toString(bundleRes);
|
String bundleStr = loadClasspath(resource);
|
||||||
Bundle bundle = ourFhirContext.newXmlParser().parseBundle(bundleStr);
|
Bundle bundle = ourFhirContext.newXmlParser().parseBundle(bundleStr);
|
||||||
|
|
||||||
List<IResource> res = new ArrayList<IResource>();
|
List<IResource> res = new ArrayList<IResource>();
|
||||||
@ -363,6 +361,7 @@ public class FhirSystemDaoDstu1Test extends BaseJpaTest {
|
|||||||
assertThat(encodeResourceToString, not(containsString("smsp")));
|
assertThat(encodeResourceToString, not(containsString("smsp")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the correct way to do this, not {@link #testTransactionWithCidIds()}
|
* This is the correct way to do this, not {@link #testTransactionWithCidIds()}
|
||||||
*/
|
*/
|
||||||
|
@ -44,6 +44,7 @@ import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
|||||||
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
|
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryResponse;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
||||||
@ -354,6 +355,42 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionCreateWithInvalidMatchUrl() {
|
||||||
|
String methodName = "testTransactionCreateWithInvalidMatchUrl";
|
||||||
|
Bundle request = new Bundle();
|
||||||
|
|
||||||
|
Patient p;
|
||||||
|
p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||||
|
EntryRequest entry = request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
|
||||||
|
|
||||||
|
try {
|
||||||
|
entry.setIfNoneExist("Patient?identifier identifier" + methodName);
|
||||||
|
mySystemDao.transaction(request);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Failed to parse match URL[Patient?identifier identifiertestTransactionCreateWithInvalidMatchUrl] - URL is invalid (must not contain spaces)", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
entry.setIfNoneExist("Patient?identifier=");
|
||||||
|
mySystemDao.transaction(request);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Invalid match URL[Patient?identifier=] - URL has no search parameters", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
entry.setIfNoneExist("Patient?foo=bar");
|
||||||
|
mySystemDao.transaction(request);
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertEquals("Failed to parse match URL[Patient?foo=bar] - Resource type Patient does not have a parameter with name: foo", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void testTransactionCreateWithDuplicateMatchUrl02() {
|
public void testTransactionCreateWithDuplicateMatchUrl02() {
|
||||||
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
|
String methodName = "testTransactionCreateWithDuplicateMatchUrl02";
|
||||||
Bundle request = new Bundle();
|
Bundle request = new Bundle();
|
||||||
|
@ -1619,6 +1619,20 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransaction() throws Exception {
|
||||||
|
String contents = loadClasspath("/update.xml");
|
||||||
|
HttpPost post = new HttpPost(ourServerBase);
|
||||||
|
post.setEntity(new StringEntity(contents, ContentType.create("application/xml+fhir", "UTF-8")));
|
||||||
|
CloseableHttpResponse resp = ourHttpClient.execute(post);
|
||||||
|
try {
|
||||||
|
assertEquals(200, resp.getStatusLine().getStatusCode());
|
||||||
|
String output= IOUtils.toString(resp.getEntity().getContent());
|
||||||
|
ourLog.info(output);
|
||||||
|
} finally {
|
||||||
|
resp.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
|
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
|
||||||
|
28
hapi-fhir-jpaserver-base/src/test/resources/update.xml
Normal file
28
hapi-fhir-jpaserver-base/src/test/resources/update.xml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Bundle xmlns="http://hl7.org/fhir">
|
||||||
|
<id value="bundle-transaction"/>
|
||||||
|
<meta>
|
||||||
|
<lastUpdated value="2014-08-18T01:43:30Z"/>
|
||||||
|
</meta>
|
||||||
|
<type value="transaction"/>
|
||||||
|
<entry>
|
||||||
|
<fullUrl value="urn:uuid:61ebe359-bfdc-4613-8bf2-c5e300945f0b"/>
|
||||||
|
<resource>
|
||||||
|
<Medication>
|
||||||
|
<code>
|
||||||
|
<coding>
|
||||||
|
<system value="http://snomed.info/sct"/>
|
||||||
|
<code value="408036003"/>
|
||||||
|
<display value="Rosuvastatin 10mg tablet"/>
|
||||||
|
</coding>
|
||||||
|
</code>
|
||||||
|
</Medication>
|
||||||
|
</resource>
|
||||||
|
<request>
|
||||||
|
<method value="POST"/>
|
||||||
|
<url value="Medication"/>
|
||||||
|
<ifNoneExist value="code=408036003"/>
|
||||||
|
</request>o
|
||||||
|
</entry>
|
||||||
|
</Bundle>
|
||||||
|
|
@ -9,6 +9,24 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<appender name="SUBSCRIPTION_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>DEBUG</level>
|
||||||
|
</filter>
|
||||||
|
<file>${fhir.logdir}/subscription.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
|
||||||
|
<fileNamePattern>${fhir.logdir}/subscription.%i.log.zip</fileNamePattern>
|
||||||
|
<minIndex>1</minIndex>
|
||||||
|
<maxIndex>10</maxIndex>
|
||||||
|
</rollingPolicy>
|
||||||
|
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||||
|
<maxFileSize>5MB</maxFileSize>
|
||||||
|
</triggeringPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{req.remoteAddr}] [%X{req.userAgent}] %-5level %logger{36} %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
<level>DEBUG</level>
|
<level>DEBUG</level>
|
||||||
@ -39,19 +57,25 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="fhirtest.access" level="INFO" additivity="false">
|
<logger name="fhirtest.access" level="INFO" additivity="false">
|
||||||
<appender-ref ref="ACCESS" />
|
<appender-ref ref="ACCESS"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" additivity="true" level="debug">
|
||||||
|
<appender-ref ref="SUBSCRIPTION_FILE"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<logger name="ca.uhn.fhir.jpa.subscription" additivity="true" level="debug">
|
||||||
|
<appender-ref ref="SUBSCRIPTION_FILE"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="debug">
|
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="debug">
|
||||||
<appender-ref ref="STDOUT" />
|
<appender-ref ref="FILE"/>
|
||||||
<appender-ref ref="FILE" />
|
<appender-ref ref="SUBSCRIPTION_FILE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
|
||||||
</appender>
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="FILE" />
|
<appender-ref ref="FILE"/>
|
||||||
</root>
|
</root>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
@ -115,6 +115,11 @@
|
|||||||
Correct issues with Android library. Thanks to
|
Correct issues with Android library. Thanks to
|
||||||
Thomas Andersen for the submission!
|
Thomas Andersen for the submission!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
JPA server incorrectly rejected match URLs
|
||||||
|
if they did not contain a question mark. Thanks
|
||||||
|
to Bill de Beaubien for reporting!
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.2" date="2015-09-18">
|
<release version="1.2" date="2015-09-18">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user