From 9658da2d3e7fceda5d977ad85fc839820fcd3c81 Mon Sep 17 00:00:00 2001 From: "John T.E. Timm" Date: Tue, 29 Oct 2019 11:06:47 -0400 Subject: [PATCH 1/3] Issue #329 - Invalid SQL generated in ResourceDAOImpl Signed-off-by: John T.E. Timm --- .../jdbc/dao/impl/ResourceDAOImpl.java | 173 +++++++----------- 1 file changed, 65 insertions(+), 108 deletions(-) diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java index d37625edd2e..527ea1d4716 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java @@ -16,6 +16,7 @@ import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -134,8 +135,7 @@ public Resource read(String logicalId, String resourceType) if (!resources.isEmpty()) { resource = resources.get(0); } - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } return resource; @@ -157,8 +157,7 @@ public Resource versionRead(String logicalId, String resourceType, int versionId if (!resources.isEmpty()) { resource = resources.get(0); } - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } return resource; @@ -184,12 +183,10 @@ protected Resource createDTO(ResultSet resultSet) throws FHIRPersistenceDataAcce resource.setLogicalId(resultSet.getString("LOGICAL_ID")); resource.setVersionId(resultSet.getInt("VERSION_ID")); resource.setDeleted(resultSet.getString("IS_DELETED").equals("Y") ? true : false); - } - catch (Throwable e) { + } catch (Throwable e) { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure creating Resource DTO."); throw severe(log, fx, e); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } @@ -211,30 +208,25 @@ public List history(String resourceType, String logicalId, Timestamp f if (this.isDb2Database()) { stmtString = stmtString + DB2_PAGINATION_PARMS; resources = this.runQuery(stmtString, logicalId, fromDateTime, maxResults, offset); - } - else { + } else { stmtString = stmtString + DERBY_PAGINATION_PARMS; resources = this.runQuery(stmtString, logicalId, fromDateTime, offset, maxResults); } - } - else { + } else { stmtString = String.format(SQL_HISTORY, resourceType, resourceType); if (this.isDb2Database()) { stmtString = stmtString + DB2_PAGINATION_PARMS; resources = this.runQuery(stmtString, logicalId, maxResults, offset); - } - else { + } else { stmtString = stmtString + DERBY_PAGINATION_PARMS; resources = this.runQuery(stmtString, logicalId, offset, maxResults); } } - } - catch (SQLException e) { + } catch (SQLException e) { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure running history query"); String errMsg = "Failure running history query: " + stmtString; throw severe(log, fx, errMsg, e); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME, Arrays.toString(new Object[] {resources})); } return resources; @@ -252,13 +244,11 @@ public int historyCount(String resourceType, String logicalId, Timestamp fromDat if (fromDateTime != null) { stmtString = String.format(SQL_HISTORY_FROM_DATETIME_COUNT, resourceType, resourceType); count = this.runCountQuery(stmtString, logicalId, fromDateTime); - } - else { + } else { stmtString = String.format(SQL_HISTORY_COUNT, resourceType, resourceType); count = this.runCountQuery(stmtString, logicalId); } - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } return count; @@ -275,8 +265,7 @@ public List search(SqlQueryData queryData) throws FHIRPersistenceDataA try { resources = this.runQuery(sqlSelect, bindVariables); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } @@ -294,8 +283,7 @@ public int searchCount(SqlQueryData queryData) throws FHIRPersistenceDataAcc try { count = this.runCountQuery(sqlSelectCount, bindVariables); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } return count; @@ -335,13 +323,11 @@ public Map readAllResourceTypeNames() dbCallDuration = (System.nanoTime()-dbCallStartTime)/1e6; log.fine("DB read all resource type complete. executionTime=" + dbCallDuration + "ms"); } - } - catch (Throwable e) { + } catch (Throwable e) { final String errMsg = "Failure retrieving all Resource type names."; FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException(errMsg); throw severe(log, fx, e); - } - finally { + } finally { this.cleanup(stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -377,16 +363,13 @@ public Integer readResourceTypeId(String resourceType) throws FHIRPersistenceDBC log.fine("DB read resource type id complete. executionTime=" + dbCallDuration + "ms"); } parameterNameId = stmt.getInt(2); - } - catch(FHIRPersistenceDBConnectException e) { + } catch(FHIRPersistenceDBConnectException e) { throw e; - } - catch (Throwable e) { + } catch (Throwable e) { final String errMsg = "Failure storing Resource type name id: name=" + resourceType; FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException(errMsg); throw severe(log, fx, e); - } - finally { + } finally { this.cleanup(stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -409,7 +392,7 @@ public List searchForIds(SqlQueryData queryData) throws FHIRPersistenceDat connection = this.getConnection(); stmt = connection.prepareStatement(queryData.getQueryString()); // Inject arguments into the prepared stmt. - for (int i = 0; i < queryData.getBindVariables().size(); i++) { + for (int i = 0; i < queryData.getBindVariables().size(); i++) { stmt.setObject(i+1, queryData.getBindVariables().get(i)); } dbCallStartTime = System.nanoTime(); @@ -418,19 +401,16 @@ public List searchForIds(SqlQueryData queryData) throws FHIRPersistenceDat if (log.isLoggable(Level.FINE)) { log.fine("DB search for ids complete. executionTime=" + dbCallDuration + "ms"); } - while(resultSet.next()) { + while(resultSet.next()) { resourceIds.add(resultSet.getLong(1)); } - } - catch(FHIRPersistenceException e) { + } catch(FHIRPersistenceException e) { throw e; - } - catch (Throwable e) { + } catch (Throwable e) { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure retrieving FHIR Resource Ids"); final String errMsg = "Failure retrieving FHIR Resource Ids. SqlQueryData=" + queryData; throw severe(log, fx, errMsg, e); - } - finally { + } finally { this.cleanup(resultSet, stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -441,6 +421,11 @@ public List searchForIds(SqlQueryData queryData) throws FHIRPersistenceDat public List searchByIds(String resourceType, List resourceIds) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException { final String METHODNAME = "searchByIds"; log.entering(CLASSNAME, METHODNAME); + + if (resourceIds.isEmpty()) { + log.exiting(CLASSNAME, METHODNAME); + return Collections.emptyList(); + } Connection connection = null; PreparedStatement stmt = null; @@ -473,16 +458,13 @@ public List searchByIds(String resourceType, List resourceIds) t log.fine("DB search by ids complete. executionTime=" + dbCallDuration + "ms"); } resources = this.createDTOs(resultSet); - } - catch(FHIRPersistenceException e) { + } catch(FHIRPersistenceException e) { throw e; - } - catch (Throwable e) { + } catch (Throwable e) { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure retrieving FHIR Resources"); errMsg = "Failure retrieving FHIR Resources. SQL=" + idQuery; throw severe(log, fx, errMsg, e); - } - finally { + } finally { this.cleanup(resultSet, stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -490,7 +472,6 @@ public List searchByIds(String resourceType, List resourceIds) t } protected String getSearchByIdsSql(String resourceType) { - String stmtString; stmtString = String.format(SQL_SEARCH_BY_IDS, resourceType, resourceType); return stmtString; @@ -515,15 +496,13 @@ public void addResourceTypeCacheCandidate(String resourceType, Integer resourceT try { trxSynchRegistry.registerInterposedSynchronization(rtCacheUpdater); log.fine("Registered ResourceTypeCacheUpdater."); - } - catch(Throwable e) { + } catch(Throwable e) { throw new FHIRPersistenceException("Failure registering ResourceTypesCacheUpdater", e); } } this.newResourceTypeIds.put(resourceType, resourceTypeId); } - log.exiting(CLASSNAME, METHODNAME); } @@ -537,12 +516,10 @@ public Resource insert(Resource resource, List parameters, ParameterD try { if (this.isDb2Database()) { resource = this.insertToDb2(resource, parameters, parameterDao); - } - else { + } else { resource = this.insertToDerby(resource, parameters, parameterDao); } - } - catch(SQLException e) { + } catch(SQLException e) { throw new FHIRPersistenceDataAccessException("Failure determining database type.",e); } @@ -584,8 +561,7 @@ private Resource insertToDb2(Resource resource, List parameters, Para acquiredFromCache = false; resourceTypeId = this.readResourceTypeId(resource.getResourceType()); this.addResourceTypeCacheCandidate(resource.getResourceType(), resourceTypeId); - } - else { + } else { acquiredFromCache = true; } if (log.isLoggable(Level.FINE)) { @@ -625,33 +601,26 @@ private Resource insertToDb2(Resource resource, List parameters, Para } } - if (log.isLoggable(Level.FINE)) { log.fine("Successfully inserted Resource. id=" + resource.getId() + " executionTime=" + dbCallDuration + "ms"); } - } - catch(FHIRPersistenceDBConnectException | FHIRPersistenceDataAccessException e) { + } catch(FHIRPersistenceDBConnectException | FHIRPersistenceDataAccessException e) { throw e; - } - catch(SQLIntegrityConstraintViolationException e) { + } catch(SQLIntegrityConstraintViolationException e) { FHIRPersistenceFKVException fx = new FHIRPersistenceFKVException("Encountered FK violation while inserting Resource."); throw severe(log, fx, e); - } - catch(SQLException e) { + } catch(SQLException e) { if ("99001".equals(e.getSQLState())) { // this is just a concurrency update, so there's no need to log the SQLException here throw new FHIRPersistenceVersionIdMismatchException("Encountered version id mismatch while inserting Resource"); - } - else { + } else { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("SQLException encountered while inserting Resource."); throw severe(log, fx, e); } - } - catch(Throwable e) { + } catch(Throwable e) { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure inserting Resource."); throw severe(log, fx, e); - } - finally { + } finally { this.cleanup(stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -692,8 +661,7 @@ private Resource insertToDerby(Resource resource, List parameters, Pa acquiredFromCache = false; resourceTypeId = derbyResourceDAO.getOrCreateResourceType(resource.getResourceType()); this.addResourceTypeCacheCandidate(resource.getResourceType(), resourceTypeId); - } - else { + } else { acquiredFromCache = true; } @@ -724,29 +692,23 @@ private Resource insertToDerby(Resource resource, List parameters, Pa if (log.isLoggable(Level.FINE)) { log.fine("Successfully inserted Resource. id=" + resource.getId() + " executionTime=" + dbCallDuration + "ms"); } - } - catch(FHIRPersistenceDBConnectException | FHIRPersistenceDataAccessException e) { + } catch(FHIRPersistenceDBConnectException | FHIRPersistenceDataAccessException e) { throw e; - } - catch(SQLIntegrityConstraintViolationException e) { + } catch(SQLIntegrityConstraintViolationException e) { FHIRPersistenceFKVException fx = new FHIRPersistenceFKVException("Encountered FK violation while inserting Resource."); throw severe(log, fx, e); - } - catch(SQLException e) { + } catch(SQLException e) { if ("99001".equals(e.getSQLState())) { // this is just a concurrency update, so there's no need to log the SQLException here throw new FHIRPersistenceVersionIdMismatchException("Encountered version id mismatch while inserting Resource"); - } - else { + } else { FHIRPersistenceFKVException fx = new FHIRPersistenceFKVException("SQLException encountered while inserting Resource."); throw severe(log, fx, e); } - } - catch(Throwable e) { + } catch(Throwable e) { FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure inserting Resource."); throw severe(log, fx, e); - } - finally { + } finally { this.cleanup(null, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -764,8 +726,7 @@ public List search(String sqlSelect) throws FHIRPersistenceDataAccessE try { resources = this.runQuery(sqlSelect); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } @@ -787,20 +748,17 @@ public List searchForIds(String sqlSelect) throws FHIRPersistenceDataAcces connection = this.getConnection(); stmt = connection.prepareStatement(sqlSelect); resultSet = stmt.executeQuery(); - while(resultSet.next()) { + while (resultSet.next()) { resourceIds.add(resultSet.getLong(1)); } - } - catch(FHIRPersistenceException e) { + } catch(FHIRPersistenceException e) { throw e; - } - catch (Throwable e) { + } catch (Throwable e) { // Log the SQL but don't expose it in the exception FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure retrieving FHIR Resource Ids."); errMsg = "Failure retrieving FHIR Resource Ids. SQL=" + sqlSelect; throw severe(log, fx, errMsg, e); - } - finally { + } finally { this.cleanup(resultSet, stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -812,6 +770,11 @@ public List searchByIds(List resourceIds) throws FHIRPersistence final String METHODNAME = "searchByIds"; log.entering(CLASSNAME, METHODNAME); + if (resourceIds.isEmpty()) { + log.exiting(CLASSNAME, METHODNAME); + return Collections.emptyList(); + } + Connection connection = null; PreparedStatement stmt = null; ResultSet resultSet = null; @@ -833,23 +796,19 @@ public List searchByIds(List resourceIds) throws FHIRPersistence connection = this.getConnection(); stmt = connection.prepareStatement(idQuery.toString()); // Inject IDs into the prepared stmt. - for (int i = 0; i < resourceIds.size(); i++) { + for (int i = 0; i < resourceIds.size(); i++) { stmt.setObject(i+1, resourceIds.get(i)); } resultSet = stmt.executeQuery(); resources = this.createDTOs(resultSet); - } - catch(FHIRPersistenceException e) { + } catch(FHIRPersistenceException e) { throw e; - } - catch (Throwable e) { + } catch (Throwable e) { // Log the SQL but don't expose it in the exception FHIRPersistenceDataAccessException fx = new FHIRPersistenceDataAccessException("Failure retrieving FHIR Resources."); errMsg = "Failure retrieving FHIR Resources. SQL=" + idQuery; throw severe(log, fx, errMsg, e); - - } - finally { + } finally { this.cleanup(resultSet, stmt, connection); log.exiting(CLASSNAME, METHODNAME); } @@ -865,8 +824,7 @@ public int searchCount(String sqlSelectCount) throws FHIRPersistenceDataAccessEx try { count = this.runCountQuery(sqlSelectCount); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } return count; @@ -882,8 +840,7 @@ public List searchStringValues(SqlQueryData queryData) Object[] bindVariables = queryData.getBindVariables().toArray(); try { return this.runQuery_STR_VALUES(sqlSelect, bindVariables); - } - finally { + } finally { log.exiting(CLASSNAME, METHODNAME); } } From 7fb714d36fef5b340fe96958a82770a16b7caaa9 Mon Sep 17 00:00:00 2001 From: "John T.E. Timm" Date: Tue, 29 Oct 2019 17:35:35 -0400 Subject: [PATCH 2/3] Issue #329 - paging context validation Signed-off-by: John T.E. Timm --- .../fhir/core/context/FHIRPagingContext.java | 6 +- .../context/impl/FHIRPagingContextImpl.java | 17 ++- .../jdbc/dao/impl/ResourceDAOImpl.java | 2 - .../jdbc/impl/FHIRPersistenceJDBCImpl.java | 105 ++++++++++--- .../persistence/util/FHIRPersistenceUtil.java | 3 +- .../test/common/AbstractPagingTest.java | 138 +++++++++++++++++- .../test/common/AbstractPersistenceTest.java | 9 +- .../resources/logging.unitTest.properties | 2 +- .../search/context/FHIRSearchContext.java | 2 - .../context/impl/FHIRSearchContextImpl.java | 1 - .../com/ibm/fhir/search/util/SearchUtil.java | 2 + .../fhir/server/resources/FHIRResource.java | 8 +- 12 files changed, 247 insertions(+), 48 deletions(-) diff --git a/fhir-core/src/main/java/com/ibm/fhir/core/context/FHIRPagingContext.java b/fhir-core/src/main/java/com/ibm/fhir/core/context/FHIRPagingContext.java index d55345b491c..342cde83ee8 100644 --- a/fhir-core/src/main/java/com/ibm/fhir/core/context/FHIRPagingContext.java +++ b/fhir-core/src/main/java/com/ibm/fhir/core/context/FHIRPagingContext.java @@ -10,9 +10,11 @@ public interface FHIRPagingContext { int getLastPageNumber(); int getPageNumber(); int getPageSize(); - long getTotalCount(); + int getTotalCount(); void setLastPageNumber(int lastPageNumber); void setPageNumber(int pageNumber); void setPageSize(int pageSize); - void setTotalCount(long totalCount); + void setTotalCount(int totalCount); + boolean isLenient(); + void setLenient(boolean lenient); } diff --git a/fhir-core/src/main/java/com/ibm/fhir/core/context/impl/FHIRPagingContextImpl.java b/fhir-core/src/main/java/com/ibm/fhir/core/context/impl/FHIRPagingContextImpl.java index f484d0ed8ea..e9c997cf53b 100644 --- a/fhir-core/src/main/java/com/ibm/fhir/core/context/impl/FHIRPagingContextImpl.java +++ b/fhir-core/src/main/java/com/ibm/fhir/core/context/impl/FHIRPagingContextImpl.java @@ -16,7 +16,8 @@ public class FHIRPagingContextImpl implements FHIRPagingContext { protected int lastPageNumber; protected int pageNumber; protected int pageSize; - protected long totalCount; + protected int totalCount; + protected boolean lenient = true; public FHIRPagingContextImpl() { this.pageNumber = DEFAULT_PAGE_NUMBER; @@ -40,7 +41,7 @@ public int getPageSize() { } @Override - public long getTotalCount() { + public int getTotalCount() { return totalCount; } @@ -60,7 +61,17 @@ public void setPageSize(int pageSize) { } @Override - public void setTotalCount(long totalCount) { + public void setTotalCount(int totalCount) { this.totalCount = totalCount; } + + @Override + public boolean isLenient() { + return lenient; + } + + @Override + public void setLenient(boolean lenient) { + this.lenient = lenient; + } } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java index 527ea1d4716..651f98470ef 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/impl/ResourceDAOImpl.java @@ -423,7 +423,6 @@ public List searchByIds(String resourceType, List resourceIds) t log.entering(CLASSNAME, METHODNAME); if (resourceIds.isEmpty()) { - log.exiting(CLASSNAME, METHODNAME); return Collections.emptyList(); } @@ -771,7 +770,6 @@ public List searchByIds(List resourceIds) throws FHIRPersistence log.entering(CLASSNAME, METHODNAME); if (resourceIds.isEmpty()) { - log.exiting(CLASSNAME, METHODNAME); return Collections.emptyList(); } diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java index b0b15316f3e..c59902b204d 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/impl/FHIRPersistenceJDBCImpl.java @@ -6,6 +6,7 @@ package com.ibm.fhir.persistence.jdbc.impl; +import static com.ibm.fhir.model.type.String.string; import static com.ibm.fhir.config.FHIRConfiguration.PROPERTY_JDBC_ENABLE_CODE_SYSTEMS_CACHE; import static com.ibm.fhir.config.FHIRConfiguration.PROPERTY_JDBC_ENABLE_PARAMETER_NAMES_CACHE; import static com.ibm.fhir.config.FHIRConfiguration.PROPERTY_JDBC_ENABLE_RESOURCE_TYPES_CACHE; @@ -41,6 +42,7 @@ import com.ibm.fhir.config.FHIRConfiguration; import com.ibm.fhir.config.PropertyGroup; import com.ibm.fhir.core.FHIRUtilities; +import com.ibm.fhir.core.context.FHIRPagingContext; import com.ibm.fhir.database.utils.api.IConnectionProvider; import com.ibm.fhir.exception.FHIRException; import com.ibm.fhir.model.format.Format; @@ -52,6 +54,7 @@ import com.ibm.fhir.model.resource.OperationOutcome; import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.resource.SearchParameter; +import com.ibm.fhir.model.type.CodeableConcept; import com.ibm.fhir.model.type.Id; import com.ibm.fhir.model.type.Instant; import com.ibm.fhir.model.type.Meta; @@ -391,13 +394,12 @@ public MultiResourceResult search(FHIRPersistenceContext context, Clas log.entering(CLASSNAME, METHODNAME); List resources = Collections.emptyList(); + MultiResourceResult.Builder resultBuilder = new MultiResourceResult.Builder<>(); FHIRSearchContext searchContext = context.getSearchContext(); JDBCQueryBuilder queryBuilder; List sortedIdList; List unsortedResultsList; int searchResultCount = 0; - int pageSize; - int lastPageNumber; SqlQueryData countQuery; SqlQueryData query; @@ -406,20 +408,26 @@ public MultiResourceResult search(FHIRPersistenceContext context, Clas (ResourceDAO)this.getResourceDao()); countQuery = queryBuilder.buildCountQuery(resourceType, searchContext); - if (countQuery != null) { + if (countQuery != null) { searchResultCount = this.getResourceDao().searchCount(countQuery); if (log.isLoggable(Level.FINE)) { log.fine("searchResultCount = " + searchResultCount); } searchContext.setTotalCount(searchResultCount); - pageSize = searchContext.getPageSize(); - lastPageNumber = (int) ((searchResultCount + pageSize - 1) / pageSize); - searchContext.setLastPageNumber(lastPageNumber); - // For _summary=count, we don't need to return any resource - if (searchResultCount > 0 && - !(searchContext.getSummaryParameter() != null - && searchContext.getSummaryParameter().equals(SummaryValueSet.COUNT))) { + List issues = validatePagingContext(searchContext); + + if (!issues.isEmpty()) { + resultBuilder.outcome(OperationOutcome.builder() + .issue(issues) + .build()); + if (!searchContext.isLenient()) { + return resultBuilder.success(false).build(); + } + } + + // For _summary=count, we don't need to return any resource + if (searchResultCount > 0 && !SummaryValueSet.COUNT.equals(searchContext.getSummaryParameter())) { query = queryBuilder.buildQuery(resourceType, searchContext); List elements = searchContext.getElementsParameters(); @@ -468,12 +476,10 @@ public MultiResourceResult search(FHIRPersistenceContext context, Clas } } - MultiResourceResult result = new MultiResourceResult.Builder<>() + return resultBuilder .success(true) .resource(resources) .build(); - - return result; } catch(FHIRPersistenceException e) { throw e; @@ -680,14 +686,13 @@ public MultiResourceResult history(FHIRPersistenceContex log.entering(CLASSNAME, METHODNAME); List resources = new ArrayList<>(); + MultiResourceResult.Builder resultBuilder = new MultiResourceResult.Builder<>(); List resourceDTOList; Map> deletedResourceVersions = new HashMap<>(); FHIRHistoryContext historyContext; int resourceCount; Instant since; Timestamp fromDateTime = null; - int pageSize; - int lastPageNumber; int offset; try { @@ -700,15 +705,21 @@ public MultiResourceResult history(FHIRPersistenceContex resourceCount = this.getResourceDao().historyCount(resourceType.getSimpleName(), logicalId, fromDateTime); historyContext.setTotalCount(resourceCount); - pageSize = historyContext.getPageSize(); - lastPageNumber = (int) ((resourceCount + pageSize - 1) / pageSize); - historyContext.setLastPageNumber(lastPageNumber); + List issues = validatePagingContext(historyContext); + if (!issues.isEmpty()) { + resultBuilder.outcome(OperationOutcome.builder() + .issue(issues) + .build()); + if (!historyContext.isLenient()) { + return resultBuilder.success(false).build(); + } + } if (resourceCount > 0) { - offset = (historyContext.getPageNumber() - 1) * pageSize; - resourceDTOList = this.getResourceDao().history(resourceType.getSimpleName(), logicalId, fromDateTime, offset, pageSize); + offset = (historyContext.getPageNumber() - 1) * historyContext.getPageSize(); + resourceDTOList = this.getResourceDao().history(resourceType.getSimpleName(), logicalId, fromDateTime, offset, historyContext.getPageSize()); for (com.ibm.fhir.persistence.jdbc.dto.Resource resourceDTO : resourceDTOList) { if (resourceDTO.isDeleted()) { deletedResourceVersions.putIfAbsent(logicalId, new ArrayList()); @@ -719,12 +730,10 @@ public MultiResourceResult history(FHIRPersistenceContex resources = this.convertResourceDTOList(resourceDTOList, resourceType); } - MultiResourceResult result = new MultiResourceResult.Builder() + return resultBuilder .success(true) .resource(resources) .build(); - - return result; } catch(FHIRPersistenceException e) { throw e; @@ -738,7 +747,57 @@ public MultiResourceResult history(FHIRPersistenceContex log.exiting(CLASSNAME, METHODNAME); } } + + /** + * Validate pageSize and pageNumber in the FHIRPagingContext and update invalid paging context parameters. + * + * @param pagingContext + * the FHIRPagingContext instance (FHIRSearchContext or FHIRHistoryContext) + * @return + * a list of operation outcome issues if the paging context has invalid parameters + */ + private List validatePagingContext(FHIRPagingContext pagingContext) { + List issues = new ArrayList<>(); + + int pageSize = pagingContext.getPageSize(); + if (pageSize < 0) { + issues.add(OperationOutcome.Issue.builder() + .severity(pagingContext.isLenient() ? IssueSeverity.WARNING : IssueSeverity.ERROR) + .code(IssueType.INVALID) + .details(CodeableConcept.builder() + .text(string("Invalid page size: " + pageSize)) + .build()) + .build()); + pagingContext.setPageSize(10); + } + + int lastPageNumber = Math.max(((pagingContext.getTotalCount() + pageSize - 1) / pageSize), 1); + pagingContext.setLastPageNumber(lastPageNumber); + int pageNumber = pagingContext.getPageNumber(); + if (pageNumber < 1) { + issues.add(OperationOutcome.Issue.builder() + .severity(pagingContext.isLenient() ? IssueSeverity.WARNING : IssueSeverity.ERROR) + .code(IssueType.INVALID) + .details(CodeableConcept.builder() + .text(string("Invalid page number: " + pageNumber)) + .build()) + .build()); + pagingContext.setPageNumber(1); + } else if (pageNumber > lastPageNumber) { + issues.add(OperationOutcome.Issue.builder() + .severity(pagingContext.isLenient() ? IssueSeverity.WARNING : IssueSeverity.ERROR) + .code(IssueType.INVALID) + .details(CodeableConcept.builder() + .text(string("Specified page number: " + pageNumber + " is greater than last page number: " + lastPageNumber)) + .build()) + .build()); + pagingContext.setPageNumber(lastPageNumber); + } + + return issues; + } + /* (non-Javadoc) * @see com.ibm.fhir.persistence.FHIRPersistence#vread(com.ibm.fhir.persistence.context.FHIRPersistenceContext, java.lang.Class, java.lang.String, java.lang.String) */ diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java index 62253fcbf64..533328d1a2b 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/util/FHIRPersistenceUtil.java @@ -25,9 +25,10 @@ public class FHIRPersistenceUtil { private final static double EARTH_RADIUS_KILOMETERS = 6371.0; // earth radius in kilometers // Parse history parameters into a FHIRHistoryContext - public static FHIRHistoryContext parseHistoryParameters(Map> queryParameters) throws FHIRPersistenceException { + public static FHIRHistoryContext parseHistoryParameters(Map> queryParameters, boolean lenient) throws FHIRPersistenceException { log.entering(FHIRPersistenceUtil.class.getName(), "parseHistoryParameters"); FHIRHistoryContext context = FHIRPersistenceContextFactory.createHistoryContext(); + context.setLenient(lenient); try { for (String name : queryParameters.keySet()) { List values = queryParameters.get(name); diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPagingTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPagingTest.java index f46672b604f..4d8256878db 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPagingTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPagingTest.java @@ -7,6 +7,7 @@ package com.ibm.fhir.persistence.test.common; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.AssertJUnit.assertNotNull; @@ -21,6 +22,7 @@ import com.ibm.fhir.config.FHIRRequestContext; import com.ibm.fhir.model.resource.Basic; +import com.ibm.fhir.model.resource.OperationOutcome; import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.test.TestUtil; import com.ibm.fhir.model.type.Code; @@ -29,11 +31,14 @@ import com.ibm.fhir.model.type.Extension; import com.ibm.fhir.model.type.Integer; import com.ibm.fhir.model.type.Meta; +import com.ibm.fhir.model.type.code.IssueSeverity; +import com.ibm.fhir.model.type.code.IssueType; import com.ibm.fhir.persistence.MultiResourceResult; import com.ibm.fhir.persistence.context.FHIRHistoryContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; -import com.ibm.fhir.persistence.exception.FHIRPersistenceException; +import com.ibm.fhir.search.context.FHIRSearchContext; +import com.ibm.fhir.search.util.SearchUtil; /** * This class contains a collection of search result sorting related tests that will be run against @@ -133,7 +138,6 @@ public void testHistoryPaging() throws Exception { assertEquals(results.size(), 1, "expected number of results"); assertEquals(results.get(0).getMeta().getVersionId().getValue(), "3", "expected version"); - historyContext = FHIRPersistenceContextFactory.createHistoryContext(); historyContext.setPageSize(1); historyContext.setPageNumber(2); @@ -156,26 +160,148 @@ public void testHistoryPaging() throws Exception { results = result.getResource(); assertEquals(results.size(), 1, "expected number of results"); assertEquals(results.get(0).getMeta().getVersionId().getValue(), "1", "expected version"); + + historyContext = FHIRPersistenceContextFactory.createHistoryContext(); + historyContext.setLenient(true); + historyContext.setPageSize(1); + historyContext.setPageNumber(0); + context = this.getPersistenceContextForHistory(historyContext); + + result = persistence.history(context, resource3.getClass(), resource3.getId().getValue()); + assertTrue(result.isSuccess()); + results = result.getResource(); + assertEquals(results.size(), 1, "expected number of results"); + assertEquals(results.get(0).getMeta().getVersionId().getValue(), "3", "expected version"); + OperationOutcome outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.WARNING); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); + + historyContext = FHIRPersistenceContextFactory.createHistoryContext(); + historyContext.setLenient(true); + historyContext.setPageSize(1); + historyContext.setPageNumber(4); + context = this.getPersistenceContextForHistory(historyContext); + + result = persistence.history(context, resource3.getClass(), resource3.getId().getValue()); + assertTrue(result.isSuccess()); + results = result.getResource(); + assertEquals(results.size(), 1, "expected number of results"); + assertEquals(results.get(0).getMeta().getVersionId().getValue(), "1", "expected version"); + outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.WARNING); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); + + historyContext = FHIRPersistenceContextFactory.createHistoryContext(); + historyContext.setLenient(false); + historyContext.setPageSize(1); + historyContext.setPageNumber(0); + context = this.getPersistenceContextForHistory(historyContext); + + result = persistence.history(context, resource3.getClass(), resource3.getId().getValue()); + assertFalse(result.isSuccess()); + results = result.getResource(); + assertEquals(results.size(), 0, "expected number of results"); + outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.ERROR); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); + + historyContext = FHIRPersistenceContextFactory.createHistoryContext(); + historyContext.setLenient(false); + historyContext.setPageSize(1); + historyContext.setPageNumber(4); + context = this.getPersistenceContextForHistory(historyContext); + + result = persistence.history(context, resource3.getClass(), resource3.getId().getValue()); + assertFalse(result.isSuccess()); + results = result.getResource(); + assertEquals(results.size(), 0, "expected number of results"); + outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.ERROR); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); } - @Test(expectedExceptions = FHIRPersistenceException.class) + @Test public void testInvalidPage0() throws Exception { Map> queryParameters; queryParameters = new HashMap<>(); queryParameters.put("_sort", Collections.singletonList("integer")); queryParameters.put("_tag", Collections.singletonList("pagingTest")); queryParameters.put("_page", Collections.singletonList("0")); - runQueryTest(Basic.class, queryParameters, 1); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); + searchContext.setLenient(true); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + assertTrue(result.isSuccess()); + assertFalse(result.getResource().isEmpty()); + OperationOutcome outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.WARNING); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); } - @Test(expectedExceptions = FHIRPersistenceException.class) + @Test + public void testInvalidPage0Strict() throws Exception { + Map> queryParameters; + queryParameters = new HashMap<>(); + queryParameters.put("_sort", Collections.singletonList("integer")); + queryParameters.put("_tag", Collections.singletonList("pagingTest")); + queryParameters.put("_page", Collections.singletonList("0")); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); + searchContext.setLenient(false); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + assertFalse(result.isSuccess()); + assertTrue(result.getResource().isEmpty()); + OperationOutcome outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.ERROR); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); + } + + @Test public void testInvalidPage4() throws Exception { Map> queryParameters; queryParameters = new HashMap<>(); queryParameters.put("_sort", Collections.singletonList("integer")); queryParameters.put("_tag", Collections.singletonList("pagingTest")); queryParameters.put("_page", Collections.singletonList("4")); - runQueryTest(Basic.class, queryParameters, 1); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); + searchContext.setLenient(true); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + assertTrue(result.isSuccess()); + assertFalse(result.getResource().isEmpty()); + OperationOutcome outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.WARNING); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); + } + + @Test + public void testInvalidPage4Strict() throws Exception { + Map> queryParameters; + queryParameters = new HashMap<>(); + queryParameters.put("_sort", Collections.singletonList("integer")); + queryParameters.put("_tag", Collections.singletonList("pagingTest")); + queryParameters.put("_page", Collections.singletonList("4")); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); + searchContext.setLenient(false); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + assertFalse(result.isSuccess()); + assertTrue(result.getResource().isEmpty()); + OperationOutcome outcome = result.getOutcome(); + assertTrue(outcome != null); + assertEquals(outcome.getIssue().size(), 1); + assertEquals(outcome.getIssue().get(0).getSeverity(), IssueSeverity.ERROR); + assertEquals(outcome.getIssue().get(0).getCode(), IssueType.INVALID); } private Meta tag(String tag) { diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPersistenceTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPersistenceTest.java index ca3c62f98a0..6d98e37186d 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPersistenceTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractPersistenceTest.java @@ -119,9 +119,12 @@ protected List runQueryTest(Class resourceType, St protected List runQueryTest(Class resourceType, Map> queryParms) throws Exception { return runQueryTest(resourceType, queryParms, null); } - + protected List runQueryTest(Class resourceType, Map> queryParms, Integer maxPageSize) throws Exception { - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParms); + return runQueryTest(SearchUtil.parseQueryParameters(resourceType, queryParms), resourceType, queryParms, maxPageSize).getResource(); + } + + protected MultiResourceResult runQueryTest(FHIRSearchContext searchContext, Class resourceType, Map> queryParms, Integer maxPageSize) throws Exception { // ensure that all the query parameters were processed into search parameters (needed because the server ignores invalid params by default) int expectedCount = 0; for (String key : queryParms.keySet()) { @@ -147,7 +150,7 @@ protected List runQueryTest(Class resourceType, Ma FHIRPersistenceContext persistenceContext = getPersistenceContextForSearch(searchContext); MultiResourceResult result = persistence.search(persistenceContext, resourceType); assertNotNull(result.getResource()); - return result.getResource(); + return result; } protected List runQueryTest(String compartmentName, String compartmentLogicalId, Class resourceType, String parmName, String parmValue) throws Exception { diff --git a/fhir-persistence/src/test/resources/logging.unitTest.properties b/fhir-persistence/src/test/resources/logging.unitTest.properties index 93299514c4b..5dbbfb739c5 100644 --- a/fhir-persistence/src/test/resources/logging.unitTest.properties +++ b/fhir-persistence/src/test/resources/logging.unitTest.properties @@ -49,4 +49,4 @@ java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter ############################################################ # Set this to FINE or higher to output the SQL statements and related debug info -com.ibm.fhir.persistence.level = INFO +com.ibm.fhir.persistence.level = FINE diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/context/FHIRSearchContext.java b/fhir-search/src/main/java/com/ibm/fhir/search/context/FHIRSearchContext.java index efb5467f8b9..37b2f71f25f 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/context/FHIRSearchContext.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/context/FHIRSearchContext.java @@ -82,6 +82,4 @@ public interface FHIRSearchContext extends FHIRPagingContext { * Set the value of the summary parameter. */ void setSummaryParameter(SummaryValueSet summary); - - } diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/context/impl/FHIRSearchContextImpl.java b/fhir-search/src/main/java/com/ibm/fhir/search/context/impl/FHIRSearchContextImpl.java index 038197e86a5..3eaa059ef4a 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/context/impl/FHIRSearchContextImpl.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/context/impl/FHIRSearchContextImpl.java @@ -23,7 +23,6 @@ * */ public class FHIRSearchContextImpl extends FHIRPagingContextImpl implements FHIRSearchContext { - private List searchParameters = new ArrayList<>(); private List sortParameters = new ArrayList<>(); private List includeParameters = new ArrayList<>(); diff --git a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java index 9753718cf2d..c2aeef402c8 100644 --- a/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java +++ b/fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java @@ -527,6 +527,7 @@ public static FHIRSearchContext parseQueryParameters(Class resourceType, Map< throws Exception { FHIRSearchContext context = FHIRSearchContextFactory.createSearchContext(); + context.setLenient(lenient); List parameters = new ArrayList<>(); // Retrieve the SearchParameters that will apply to this resource type (including those for Resource.class). @@ -859,6 +860,7 @@ public static boolean isSearchResultParameter(String name) { private static void parseSearchResultParameter(Class resourceType, FHIRSearchContext context, String name, List values, boolean lenient) throws FHIRSearchException { try { String first = values.get(0); + // pageSize and pageNumber validation occurs in the persistence layer if (SearchConstants.COUNT.equals(name)) { int pageSize = Integer.parseInt(first); diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java b/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java index fef8e8afc45..c0df413d155 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java @@ -1705,7 +1705,7 @@ public Resource doRead(String type, String id, boolean throwExcOnNull, boolean i FHIRSearchContext searchContext = null; if (queryParameters != null) { - searchContext = SearchUtil.parseQueryParameters(null, null, resourceType, queryParameters, httpServletRequest.getQueryString(), isSearchLenient(requestProperties)); + searchContext = SearchUtil.parseQueryParameters(null, null, resourceType, queryParameters, httpServletRequest.getQueryString(), isLenient(requestProperties)); } // Start a new txn in the persistence layer if one is not already active. @@ -1923,7 +1923,7 @@ public Bundle doHistory(String type, String id, MultivaluedMap q Class resourceType = (Class) getResourceType(resourceTypeName); FHIRHistoryContext historyContext = - FHIRPersistenceUtil.parseHistoryParameters(queryParameters); + FHIRPersistenceUtil.parseHistoryParameters(queryParameters, isLenient(requestProperties)); // Start a new txn in the persistence layer if one is not already active. txn.begin(); @@ -2032,7 +2032,7 @@ public Bundle doSearch(String type, String compartment, String compartmentId, getInterceptorMgr().fireBeforeSearchEvent(event); FHIRSearchContext searchContext = - SearchUtil.parseQueryParameters(compartment, compartmentId, resourceType, queryParameters, httpServletRequest.getQueryString(), isSearchLenient(requestProperties)); + SearchUtil.parseQueryParameters(compartment, compartmentId, resourceType, queryParameters, httpServletRequest.getQueryString(), isLenient(requestProperties)); FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(event, searchContext); @@ -2088,7 +2088,7 @@ public Bundle doSearch(String type, String compartment, String compartmentId, } } - private boolean isSearchLenient(Map requestProperties) { + private boolean isLenient(Map requestProperties) { boolean lenient = true; String handlingStringValue = getHeaderValue(requestProperties, "Prefer", "handling"); From 301c86ad5aea1311e4d03f583698b42c88277489 Mon Sep 17 00:00:00 2001 From: "John T.E. Timm" Date: Tue, 29 Oct 2019 17:39:06 -0400 Subject: [PATCH 3/3] Issue #329 - remove Math.intExact casts on total count Signed-off-by: John T.E. Timm --- .../main/java/com/ibm/fhir/server/resources/FHIRResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java b/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java index c0df413d155..c9a56264400 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/resources/FHIRResource.java @@ -3738,7 +3738,7 @@ private Bundle createSearchBundle(List resources, FHIRSearchContext se throws FHIROperationException { // throws if we have a count of more than 2,147,483,647 resources - UnsignedInt totalCount = com.ibm.fhir.model.type.UnsignedInt.of(Math.toIntExact(searchContext.getTotalCount())); + UnsignedInt totalCount = UnsignedInt.of(searchContext.getTotalCount()); // generate ID for this bundle and set total Bundle.Builder bundleBuider = Bundle.builder() .type(BundleType.SEARCHSET) @@ -3781,7 +3781,7 @@ private Bundle createHistoryBundle(List resources, FHIRHisto throws FHIROperationException { // throws if we have a count of more than 2,147,483,647 resources - UnsignedInt totalCount = com.ibm.fhir.model.type.UnsignedInt.of(Math.toIntExact(historyContext.getTotalCount())); + UnsignedInt totalCount = UnsignedInt.of(historyContext.getTotalCount()); // generate ID for this bundle and set the "total" field for the bundle Bundle.Builder bundleBuilder = Bundle.builder() .type(BundleType.HISTORY)