Merge remote-tracking branch 'upstream/master'

This commit is contained in:
bdenton 2018-11-09 14:03:51 -08:00
commit 0f39cc2d71
11 changed files with 161 additions and 63 deletions

View File

@ -404,8 +404,8 @@ class ModelScanner {
if (paramType == null) {
throw new ConfigurationException("Search param " + searchParam.name() + " has an invalid type: " + searchParam.type());
}
Set<String> providesMembershipInCompartments = null;
providesMembershipInCompartments = new HashSet<String>();
Set<String> providesMembershipInCompartments;
providesMembershipInCompartments = new HashSet<>();
for (Compartment next : searchParam.providesMembershipIn()) {
if (paramType != RestSearchParameterTypeEnum.REFERENCE) {
StringBuilder b = new StringBuilder();
@ -427,7 +427,8 @@ class ModelScanner {
}
RuntimeSearchParam param = new RuntimeSearchParam(searchParam.name(), searchParam.description(), searchParam.path(), paramType, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE);
Collection<String> base = Collections.singletonList(theResourceDef.getName());
RuntimeSearchParam param = new RuntimeSearchParam(null, null, searchParam.name(), searchParam.description(), searchParam.path(), paramType, null, providesMembershipInCompartments, toTargetList(searchParam.target()), RuntimeSearchParamStatusEnum.ACTIVE, base);
theResourceDef.addSearchParam(param);
nameToParam.put(param.getName(), param);
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.api;
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public enum RequestFormatParamStyleEnum {
/**
* Do not include a _format parameter on requests

View File

@ -17,9 +17,9 @@ import java.util.List;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.client.impl;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -638,7 +638,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
if (myDaoConfig.isMarkResourcesForReindexingUponSearchParameterChange()) {
if (isNotBlank(theExpression)) {
if (isNotBlank(theExpression) && theExpression.contains(".")) {
final String resourceType = theExpression.substring(0, theExpression.indexOf('.'));
ourLog.debug("Marking all resources of type {} for reindexing due to updated search parameter with path: {}", resourceType, theExpression);

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -13,7 +13,11 @@ import ca.uhn.fhir.util.ElementUtil;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.utils.FHIRLexer;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@ -42,6 +46,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchParameter> implements IFhirResourceDaoSearchParameter<SearchParameter> {
public static final DefaultProfileValidationSupport VALIDATION_SUPPORT = new DefaultProfileValidationSupport();
@Autowired
private IFhirSystemDao<Bundle, Meta> mySystemDao;
@ -88,7 +93,7 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
throw new UnprocessableEntityException("SearchParameter.status is missing or invalid");
}
if (ElementUtil.isEmpty(theBase)) {
if (ElementUtil.isEmpty(theBase) && (theType == null || !Enumerations.SearchParamType.COMPOSITE.name().equals(theType.name()))) {
throw new UnprocessableEntityException("SearchParameter.base is missing");
}
@ -104,35 +109,46 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
theExpression = theExpression.trim();
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(theExpression);
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
if (!theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.R4)) {
String[] expressionSplit = BaseSearchParamExtractor.SPLIT.split(theExpression);
for (String nextPath : expressionSplit) {
nextPath = nextPath.trim();
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
int dotIdx = nextPath.indexOf('.');
if (dotIdx == -1) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\". Must start with a resource name");
}
String resourceName = nextPath.substring(0, dotIdx);
try {
theContext.getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
String resourceName = nextPath.substring(0, dotIdx);
try {
theContext.getResourceDefinition(resourceName);
} catch (DataFormatException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
}
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
if (theDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
IBaseResource temporaryInstance = theContext.getResourceDefinition(resourceName).newInstance();
try {
theContext.newFluentPath().evaluate(temporaryInstance, nextPath, IBase.class);
} catch (Exception e) {
String msg = theContext.getLocalizer().getMessage(FhirResourceDaoSearchParameterR4.class, "invalidSearchParamExpression", nextPath, e.getMessage());
throw new UnprocessableEntityException(msg, e);
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
if (theDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
IBaseResource temporaryInstance = theContext.getResourceDefinition(resourceName).newInstance();
try {
theContext.newFluentPath().evaluate(temporaryInstance, nextPath, IBase.class);
} catch (Exception e) {
String msg = theContext.getLocalizer().getMessage(FhirResourceDaoSearchParameterR4.class, "invalidSearchParamExpression", nextPath, e.getMessage());
throw new UnprocessableEntityException(msg, e);
}
}
}
}
}
} else {
FHIRPathEngine fhirPathEngine = new FHIRPathEngine(new HapiWorkerContext(theContext, VALIDATION_SUPPORT));
try {
fhirPathEngine.parse(theExpression);
} catch (FHIRLexer.FHIRLexerException e) {
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + theExpression + "\": " + e.getMessage());
}
}
} // if have expression
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao.r4;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -14,10 +14,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.junit.*;
import org.mockito.internal.util.collections.ListUtil;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
@ -61,6 +58,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
}
@Test
@Ignore
public void testCreateInvalidParamInvalidResourceName() {
SearchParameter fooSp = new SearchParameter();
fooSp.addBase("Patient");
@ -96,6 +94,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
}
@Test
@Ignore
public void testCreateInvalidParamNoResourceName() {
SearchParameter fooSp = new SearchParameter();
fooSp.addBase("Patient");
@ -243,29 +242,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
}
@Test
public void testIndexFailsIfInvalidSearchParameterExists() {
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
SearchParameter threadIdSp = new SearchParameter();
threadIdSp.addBase("Communication");
threadIdSp.setCode("has-attachments");
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(threadIdSp, mySrd);
mySearchParamRegsitry.forceRefresh();
Communication com = new Communication();
com.setStatus(Communication.CommunicationStatus.INPROGRESS);
try {
myCommunicationDao.create(com, mySrd);
fail();
} catch (InternalErrorException e) {
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": org.hl7.fhir"));
}
}
@Test
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingDisabled() {
@ -430,7 +407,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
mySearchParameterDao.create(threadIdSp, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), startsWith("The expression \"Communication.payload[1].contentAttachment is not null\" can not be evaluated and may be invalid: "));
assertThat(e.getMessage(), startsWith("Invalid SearchParameter.expression value \"Communication.payload[1].contentAttachment is not null\""));
}
}

View File

@ -0,0 +1,80 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class FhirResourceDaoSearchParameterR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoSearchParameterR4Test.class);
private FhirContext myCtx;
private FhirResourceDaoSearchParameterR4 myDao;
@Before
public void before() {
myCtx = FhirContext.forR4();
myDao = new FhirResourceDaoSearchParameterR4();
myDao.setContext(myCtx);
myDao.setConfig(new DaoConfig());
}
@Test
public void testValidateAllBuiltInSearchParams() {
for (String nextResource : myCtx.getResourceNames()) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(nextResource);
for (RuntimeSearchParam nextp : nextResDef.getSearchParams()) {
if (nextp.getName().equals("_id")) {
continue;
}
if (nextp.getName().equals("_language")) {
continue;
}
if (isBlank(nextp.getPath())) {
continue;
}
SearchParameter nextSearchParameter = new SearchParameter();
nextSearchParameter.setExpression(nextp.getPath());
nextSearchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
nextSearchParameter.setType(Enumerations.SearchParamType.fromCode(nextp.getParamType().getCode()));
nextp.getBase().forEach(t -> nextSearchParameter.addBase(t));
ourLog.info("Validating {}.{}", nextResource, nextp.getName());
myDao.validateResourceForStorage(nextSearchParameter, null);
}
}
}
@Test
public void testValidateInvalidExpression() {
SearchParameter nextSearchParameter = new SearchParameter();
nextSearchParameter.setExpression("Patient////");
nextSearchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
nextSearchParameter.setType(Enumerations.SearchParamType.STRING);
nextSearchParameter.addBase("Patient");
try {
myDao.validateResourceForStorage(nextSearchParameter, null);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Invalid SearchParameter.expression value \"Patient////\": Error at 1, 1: Premature ExpressionNode termination at unexpected token \"////\"", e.getMessage());
}
}
}

View File

@ -165,7 +165,7 @@
and only option previously) or allow any IDs including alphanumeric.
</action>
<action type="add" issue="1103" dev="ruthakm">
It is now possible to use your own IMessageResolver instance in the narrative
It is now possible to use your own IMessageResolver instance in the narrative
generator. Thanks to Ruth Alkema for the pull request!
</action>
<action type="fix" issue="1071" dev="volsch">
@ -177,6 +177,10 @@
<![CDATA[<code>_format</code>]]>
parameter should be included in requests.
</action>
<action type="add">
JPA server R4 SearchParameter custom expression validation is now done using the
actual FHIRPath evaluator, meaning it is more rigorous in what it can find.
</action>
</release>
<release version="3.5.0" date="2018-09-17">