Expand search range when searching by date

This commit is contained in:
James Agnew 2018-10-03 21:31:01 -04:00
parent 6ce9120132
commit b265c0281b
20 changed files with 204 additions and 53 deletions

View File

@ -23,21 +23,23 @@ package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterOr;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.model.primitive.BaseDateTimeDt;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ObjectUtil;
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -222,7 +224,7 @@ public class DateParam extends BaseParamWithPrefix<DateParam> implements /*IQuer
if (theParameters.size() == 1) {
setValueAsString(theParameters.get(0));
} else if (theParameters.size() > 1) {
throw new InvalidRequestException("This server does not support multi-valued dates for this paramater: " + theParameters);
throw new InvalidRequestException("This server does not support multi-valued dates for this parameter: " + theParameters);
}
}

View File

@ -2,15 +2,14 @@ package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.QualifiedParamList;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.*;
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
import static java.lang.String.format;
@ -260,6 +259,14 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
return null;
}
Date retVal = myLowerBound.getValue();
if (myLowerBound.getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal()) {
Calendar cal = DateUtils.toCalendar(retVal);
cal.setTimeZone(TimeZone.getTimeZone("GMT-11:30"));
cal = DateUtils.truncate(cal, Calendar.DATE);
retVal = cal.getTime();
}
if (myLowerBound.getPrefix() != null) {
switch (myLowerBound.getPrefix()) {
case GREATERTHAN:
@ -306,7 +313,16 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
if (myUpperBound == null) {
return null;
}
Date retVal = myUpperBound.getValue();
if (myLowerBound.getPrecision().ordinal() <= TemporalPrecisionEnum.DAY.ordinal()) {
Calendar cal = DateUtils.toCalendar(retVal);
cal.setTimeZone(TimeZone.getTimeZone("GMT+11:30"));
cal = DateUtils.truncate(cal, Calendar.DATE);
retVal = cal.getTime();
}
if (myUpperBound.getPrefix() != null) {
switch (myUpperBound.getPrefix()) {
case LESSTHAN:

View File

@ -87,7 +87,7 @@ public class ParametersUtil {
addClientParameter(theContext, next, theTargetResource, paramChild, paramChildElem, theName);
}
} else {
throw new IllegalArgumentException("Don't know how to handle value of type " + theValue.getClass() + " for paramater " + theName);
throw new IllegalArgumentException("Don't know how to handle value of type " + theValue.getClass() + " for parameter " + theName);
}
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.config;
* 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

@ -22,9 +22,9 @@ import java.util.*;
* 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

@ -1,7 +1,28 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.rest.param.HasAndListParam;
import org.apache.commons.lang3.Validate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
@ -17,27 +38,32 @@ public class DaoRegistry implements ApplicationContextAware {
@Autowired
private FhirContext myCtx;
private Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao = new HashMap<>();
private volatile Map<String, IFhirResourceDao<?>> myResourceNameToResourceDao = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
myAppCtx = theApplicationContext;
}
@PostConstruct
public void start() {
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
for (IFhirResourceDao nextResourceDao : resourceDaos.values()) {
RuntimeResourceDefinition nextResourceDef = myCtx.getResourceDefinition(nextResourceDao.getResourceType());
myResourceNameToResourceDao.put(nextResourceDef.getName(), nextResourceDao);
}
}
public IFhirResourceDao<?> getResourceDao(String theResourceName) {
IFhirResourceDao<?> retVal = myResourceNameToResourceDao.get(theResourceName);
IFhirResourceDao<?> retVal = getResourceNameToResourceDao().get(theResourceName);
Validate.notNull(retVal, "No DAO exists for resource type %s", theResourceName);
return retVal;
}
private Map<String, IFhirResourceDao<?>> getResourceNameToResourceDao() {
Map<String, IFhirResourceDao<?>> retVal = myResourceNameToResourceDao;
if (retVal == null) {
retVal = new HashMap<>();
Map<String, IFhirResourceDao> resourceDaos = myAppCtx.getBeansOfType(IFhirResourceDao.class);
for (IFhirResourceDao nextResourceDao : resourceDaos.values()) {
RuntimeResourceDefinition nextResourceDef = myCtx.getResourceDefinition(nextResourceDao.getResourceType());
retVal.put(nextResourceDef.getName(), nextResourceDao);
}
myResourceNameToResourceDao = retVal;
}
return retVal;
}
}

View File

@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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%
*/
import java.util.Iterator;
public interface IResultIterator extends Iterator<Long> {

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

@ -997,6 +997,8 @@ public class SearchBuilder implements ISearchBuilder {
}
}
ourLog.trace("Date range is {} - {}", lowerBound, upperBound);
if (lb != null && ub != null) {
return (theBuilder.and(lb, ub));
} else if (lb != null) {

View File

@ -31,9 +31,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* 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

@ -24,9 +24,9 @@ import static org.apache.commons.lang3.StringUtils.left;
* 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.jpa.entity;
* 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

@ -29,16 +29,15 @@ import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.ValidateUtil;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.IdType;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
@ -54,7 +53,7 @@ public class SubscriptionRetriggeringProvider implements IResourceProvider {
private List<BaseSubscriptionInterceptor<?>> mySubscriptionInterceptorList;
@Operation(name= JpaConstants.OPERATION_RETRIGGER_SUBSCRIPTION)
public IBaseOperationOutcome reTriggerSubscription(
public IBaseParameters reTriggerSubscription(
@IdParam IIdType theSubscriptionId,
@OperationParam(name= RESOURCE_ID) UriParam theResourceId) {
@ -77,8 +76,10 @@ public class SubscriptionRetriggeringProvider implements IResourceProvider {
next.submitResourceModified(msg);
}
IBaseOperationOutcome retVal = OperationOutcomeUtil.newInstance(myFhirContext);
OperationOutcomeUtil.addIssue(myFhirContext, retVal, "information", "Triggered resource " + theResourceId.getValue() + " for subscription", null, null);
IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
IPrimitiveType<?> value = (IPrimitiveType<?>) myFhirContext.getElementDefinition("string").newInstance();
value.setValueAsString("Triggered resource " + theResourceId.getValue() + " for subscription");
ParametersUtil.addParameterToParameters(myFhirContext, retVal, "information", value);
return retVal;
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.search;
* 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.jpa.search;
* 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

@ -105,7 +105,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
DataSource dataSource = ProxyDataSourceBuilder
.create(retVal)
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
.countQuery(new ThreadQueryCountHolder())
.build();

View File

@ -38,10 +38,7 @@ import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@ -3227,6 +3224,79 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
}
@Test
public void testDateSearchParametersShouldBeTimezoneIndependent() {
createObservationWithEffective("NO1", "2011-01-02T23:00:00-11:30");
createObservationWithEffective("NO2", "2011-01-03T00:00:00+01:00");
createObservationWithEffective("YES01", "2011-01-02T00:00:00-11:30");
createObservationWithEffective("YES02", "2011-01-02T00:00:00-10:00");
createObservationWithEffective("YES03", "2011-01-02T00:00:00-09:00");
createObservationWithEffective("YES04", "2011-01-02T00:00:00-08:00");
createObservationWithEffective("YES05", "2011-01-02T00:00:00-07:00");
createObservationWithEffective("YES06", "2011-01-02T00:00:00-06:00");
createObservationWithEffective("YES07", "2011-01-02T00:00:00-05:00");
createObservationWithEffective("YES08", "2011-01-02T00:00:00-04:00");
createObservationWithEffective("YES09", "2011-01-02T00:00:00-03:00");
createObservationWithEffective("YES10", "2011-01-02T00:00:00-02:00");
createObservationWithEffective("YES11", "2011-01-02T00:00:00-01:00");
createObservationWithEffective("YES12", "2011-01-02T00:00:00Z");
createObservationWithEffective("YES13", "2011-01-02T00:00:00+01:00");
createObservationWithEffective("YES14", "2011-01-02T00:00:00+02:00");
createObservationWithEffective("YES15", "2011-01-02T00:00:00+03:00");
createObservationWithEffective("YES16", "2011-01-02T00:00:00+04:00");
createObservationWithEffective("YES17", "2011-01-02T00:00:00+05:00");
createObservationWithEffective("YES18", "2011-01-02T00:00:00+06:00");
createObservationWithEffective("YES19", "2011-01-02T00:00:00+07:00");
createObservationWithEffective("YES20", "2011-01-02T00:00:00+08:00");
createObservationWithEffective("YES21", "2011-01-02T00:00:00+09:00");
createObservationWithEffective("YES22", "2011-01-02T00:00:00+10:00");
createObservationWithEffective("YES23", "2011-01-02T00:00:00+11:00");
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Observation.SP_DATE, new DateParam("2011-01-02"));
IBundleProvider results = myObservationDao.search(map);
List<String> values = toUnqualifiedVersionlessIdValues(results);
Collections.sort(values);
assertThat(values.toString(), values, contains(
"Observation/YES01",
"Observation/YES02",
"Observation/YES03",
"Observation/YES04",
"Observation/YES05",
"Observation/YES06",
"Observation/YES07",
"Observation/YES08",
"Observation/YES09",
"Observation/YES10",
"Observation/YES11",
"Observation/YES12",
"Observation/YES13",
"Observation/YES14",
"Observation/YES15",
"Observation/YES16",
"Observation/YES17",
"Observation/YES18",
"Observation/YES19",
"Observation/YES20",
"Observation/YES21",
"Observation/YES22",
"Observation/YES23"
));
}
private void createObservationWithEffective(String theId, String theEffective) {
Observation obs = new Observation();
obs.setId(theId);
obs.setEffective(new DateTimeType(theEffective));
myObservationDao.update(obs);
ourLog.info("Obs {} has time {}", theId, obs.getEffectiveDateTimeType().getValue().toString());
}
/**
* See #744
*/

View File

@ -144,8 +144,8 @@ public class RetriggeringDstu3Test extends BaseResourceProviderDstu3Test {
.withParameter(Parameters.class, SubscriptionRetriggeringProvider.RESOURCE_ID, new UriType(obsId.toUnqualifiedVersionless().getValue()))
.execute();
OperationOutcome oo = (OperationOutcome) response.getParameter().get(0).getResource();
assertEquals("Triggered resource " + obsId.getValue() + " for subscription", oo.getIssue().get(0).getDiagnostics());
String responseValue = response.getParameter().get(0).getValue().primitiveValue();
assertEquals("Triggered resource " + obsId.getValue() + " for subscription", responseValue);
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
@ -181,8 +181,8 @@ public class RetriggeringDstu3Test extends BaseResourceProviderDstu3Test {
.withParameter(Parameters.class, SubscriptionRetriggeringProvider.RESOURCE_ID, new UriType(obsId.toUnqualifiedVersionless().getValue()))
.execute();
OperationOutcome oo = (OperationOutcome) response.getParameter().get(0).getResource();
assertEquals("Triggered resource " + obsId.getValue() + " for subscription", oo.getIssue().get(0).getDiagnostics());
String responseValue = response.getParameter().get(0).getValue().primitiveValue();
assertEquals("Triggered resource " + obsId.getValue() + " for subscription", responseValue);
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);

View File

@ -39,6 +39,14 @@ public class DateRangeParamTest {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Test
public void testGetLowerRange() {
ourLog.info("Time is {}", new Date());
DateRangeParam param = new DateRangeParam(new DateParam("2011-01-02"));
ourLog.info("Adjusted time is " + param.getLowerBoundAsInstant().toString());
}
private static DateRangeParam create(String theLower, String theUpper) throws InvalidRequestException {
DateRangeParam p = new DateRangeParam();
List<QualifiedParamList> tokens = new ArrayList<QualifiedParamList>();

View File

@ -65,6 +65,12 @@
<![CDATA[<code>DatConfig#setSearchPreFetchThresholds()</code>]]>
for configuration of this feature.
</action>
<action type="add">
When performing a JPA server using a date parameter, if a time is not specified in
the query URL, the date range is expanded slightly to include all possible
timezones where the date that could apply. This makes the search slightly more
inclusive, which errs on the side of caution.
</action>
</release>
<release version="3.5.0" date="2018-09-17">