diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/dto/ReadResultDTO.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/dto/ReadResultDTO.java index f40c2f28d76..a307b37d805 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/dto/ReadResultDTO.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/dto/ReadResultDTO.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.ibm.fhir.model.resource.Resource; @@ -26,23 +27,26 @@ public ReadResultDTO() { // No Operation } - public ReadResultDTO(List resources) { + public ReadResultDTO(List resources) { this.resources.addAll(resources); } /** * @return the resources */ - public List getResources() { - return resources; + public List getResources() { + return Collections.unmodifiableList(this.resources); } /** + * Replace the contents of the internal resources list with the contents + * of the given resources list * @param resources * the resources to set */ - public void setResources(List resources) { - this.resources = resources; + public void setResources(List resources) { + this.resources.clear(); + this.resources.addAll(resources); } public void addResource(Resource resource) { diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/patient/resource/PatientResourceHandler.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/patient/resource/PatientResourceHandler.java index 45eab8be72a..ee52598c24f 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/patient/resource/PatientResourceHandler.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/patient/resource/PatientResourceHandler.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,6 +26,7 @@ import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.operation.bulkdata.model.type.BulkDataContext; import com.ibm.fhir.persistence.FHIRPersistence; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; import com.ibm.fhir.search.SearchConstants; @@ -128,8 +129,8 @@ public List executeSearch(Set patientIds) throws Exception { FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(null, searchContext); Date startTime = new Date(System.currentTimeMillis()); - List resources = fhirPersistence.search(persistenceContext, resourceType).getResource(); - + List> resourceResults = fhirPersistence.search(persistenceContext, resourceType).getResourceResults(); + List resources = ResourceResult.toResourceList(resourceResults); if (isDoDuplicationCheck) { resources = resources.stream() // the add returns false if the id already exists, which filters it out of the collection diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/system/resource/SystemExportResourceHandler.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/system/resource/SystemExportResourceHandler.java index 98b8c50187c..53c572fb2a9 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/system/resource/SystemExportResourceHandler.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/system/resource/SystemExportResourceHandler.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,7 +35,7 @@ public SystemExportResourceHandler() { // No Operation } - public void fillChunkData(String exportFormat, ExportTransientUserData chunkData, List resources) throws Exception { + public void fillChunkData(String exportFormat, ExportTransientUserData chunkData, List resources) throws Exception { int resSubTotal = 0; if (chunkData == null) { String msg = "fillChunkDataBuffer: chunkData is null, this should never happen!"; diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/writer/SparkParquetWriter.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/writer/SparkParquetWriter.java index 6abc3f39aff..e4d301836e6 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/writer/SparkParquetWriter.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/export/writer/SparkParquetWriter.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -82,7 +82,7 @@ public SparkParquetWriter(boolean useIAM, String cosEndpoint, String apiKeyOrAcc * @throws FHIRGeneratorException * @implnote If a cos URI is passed, the file will be created if needed. For a file URI */ - public void writeParquet(List resources, String outDirName) + public void writeParquet(List resources, String outDirName) throws FHIRGeneratorException { List jsonResources = new ArrayList<>(); diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/ChunkReader.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/ChunkReader.java index 3394a96d380..daf82dcae5c 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/ChunkReader.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/patient/ChunkReader.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -50,6 +50,7 @@ import com.ibm.fhir.operation.bulkdata.model.type.BulkDataContext; import com.ibm.fhir.operation.bulkdata.model.type.OperationFields; import com.ibm.fhir.persistence.FHIRPersistence; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; import com.ibm.fhir.persistence.helper.FHIRPersistenceHelper; @@ -203,7 +204,8 @@ public Object readItem() throws Exception { try { FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(null, searchContext); Date startTime = new Date(System.currentTimeMillis()); - List patientResources = fhirPersistence.search(persistenceContext, Patient.class).getResource(); + List> resourceResults = fhirPersistence.search(persistenceContext, Patient.class).getResourceResults(); + List patientResources = ResourceResult.toResourceList(resourceResults); if (isDoDuplicationCheck) { patientResources = patientResources.stream() .filter(r -> loadedPatientIds.add(r.getId())) @@ -248,7 +250,7 @@ public Object readItem() throws Exception { if (!patientIds.isEmpty()) { handler.register(chunkData, ctx, fhirPersistence, pageSize, resourceType, searchParametersForResoureTypes, ctx.getSource()); - List resources = Patient.class.isAssignableFrom(resourceType) ? + List resources = Patient.class.isAssignableFrom(resourceType) ? patientResources : handler.executeSearch(patientIds); if (FHIRMediaType.APPLICATION_PARQUET.equals(ctx.getFhirExportFormat())) { dto.setResources(resources); diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/system/ChunkReader.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/system/ChunkReader.java index 8d7d4aad752..0c088d54a97 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/system/ChunkReader.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/jbatch/export/system/ChunkReader.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -48,6 +48,7 @@ import com.ibm.fhir.operation.bulkdata.model.type.BulkDataContext; import com.ibm.fhir.operation.bulkdata.model.type.OperationFields; import com.ibm.fhir.persistence.FHIRPersistence; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; import com.ibm.fhir.persistence.helper.FHIRPersistenceHelper; @@ -211,7 +212,7 @@ public Object readItem() throws Exception { FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters); searchContext.setPageSize(pageSize); searchContext.setPageNumber(pageNum); - List resources = null; + List resources = null; ReadResultDTO dto = new ReadResultDTO(); @@ -223,7 +224,9 @@ public Object readItem() throws Exception { try { // Execute the search query to obtain the page of resources persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(null, searchContext); - resources = fhirPersistence.search(persistenceContext, resourceType).getResource(); + List> resourceResults = fhirPersistence.search(persistenceContext, resourceType).getResourceResults(); + resources = ResourceResult.toResourceList(resourceResults); + if (isDoDuplicationCheck) { resources = resources.stream() // the add returns false if the id already exists, which filters it out of the collection diff --git a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/provider/impl/S3Provider.java b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/provider/impl/S3Provider.java index e747cecc6ba..547f59606ae 100644 --- a/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/provider/impl/S3Provider.java +++ b/fhir-bulkdata-webapp/src/main/java/com/ibm/fhir/bulkdata/provider/impl/S3Provider.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -442,7 +442,7 @@ private void pushFhirJsonsToCos(InputStream in, int dataLength) throws Exception } } - private void pushFhirParquetToCos(List resources) throws Exception { + private void pushFhirParquetToCos(List resources) throws Exception { if (chunkData == null) { logger.warning("pushFhirParquetToCos: chunkData is null, this should never happen!"); throw new Exception("pushFhirParquetToCos: chunkData is null, this should never happen!"); diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/api/ResourceDAO.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/api/ResourceDAO.java index 465626baf4c..34d50abd725 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/api/ResourceDAO.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/dao/api/ResourceDAO.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -111,11 +111,12 @@ List search(String sqlSelect) * Searches for Resources that contain one of the passed ids. * @param resourceType - The type of the FHIR Resource * @param resourceIds - A List of resource ids. + * @param includeResourceData - fetch the resource DATA column * @return List - A List of resources matching the the passed list of ids. * @throws FHIRPersistenceDataAccessException * @throws FHIRPersistenceDBConnectException */ - List searchByIds(String resourceType, List resourceIds) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException; + List searchByIds(String resourceType, List resourceIds, boolean includeResourceData) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException; /** * Executes a count query based on the data contained in the passed {@link Select} statement, using its encapsulated search string and bind variables. 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 62b05bd4864..08c24049058 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -127,6 +127,10 @@ public class ResourceDAOImpl extends FHIRDbDAOImpl implements ResourceDAO { "SELECT R.RESOURCE_ID, R.LOGICAL_RESOURCE_ID, R.VERSION_ID, R.LAST_UPDATED, R.IS_DELETED, R.DATA, LR.LOGICAL_ID, R.RESOURCE_PAYLOAD_KEY " + "FROM %s_RESOURCES R, %s_LOGICAL_RESOURCES LR WHERE R.LOGICAL_RESOURCE_ID = LR.LOGICAL_RESOURCE_ID AND " + "R.RESOURCE_ID IN "; + private static final String SQL_SEARCH_BY_IDS_NO_DATA = + "SELECT R.RESOURCE_ID, R.LOGICAL_RESOURCE_ID, R.VERSION_ID, R.LAST_UPDATED, R.IS_DELETED, CAST(NULL AS BLOB) AS DATA, LR.LOGICAL_ID, R.RESOURCE_PAYLOAD_KEY " + + "FROM %s_RESOURCES R, %s_LOGICAL_RESOURCES LR WHERE R.LOGICAL_RESOURCE_ID = LR.LOGICAL_RESOURCE_ID AND " + + "R.RESOURCE_ID IN "; private static final String SQL_ORDER_BY_IDS = "ORDER BY CASE R.RESOURCE_ID "; @@ -658,7 +662,7 @@ public List search(String sqlSelect) throws FHIRPersistenceDataAccessE } @Override - public List searchByIds(String resourceType, List resourceIds) + public List searchByIds(String resourceType, List resourceIds, boolean includeResourceData) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException { final String METHODNAME = "searchByIds"; log.entering(CLASSNAME, METHODNAME); @@ -678,7 +682,11 @@ public List searchByIds(String resourceType, List resourceIds) double dbCallDuration; try { - stmtString = getSearchByIdsSql(resourceType); + if (includeResourceData) { + stmtString = getSearchByIdsSql(resourceType); + } else { + stmtString = getSearchByIdsNoDataSql(resourceType); + } idQuery.append(stmtString); idQuery.append("("); // resourceIds should have a max length of 1000 (the max page size) @@ -719,6 +727,10 @@ protected String getSearchByIdsSql(String resourceType) { return String.format(SQL_SEARCH_BY_IDS, resourceType, resourceType); } + protected String getSearchByIdsNoDataSql(String resourceType) { + return String.format(SQL_SEARCH_BY_IDS_NO_DATA, resourceType, resourceType); + } + @Override public int searchCount(String sqlSelectCount) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException { final String METHODNAME = "searchCount"; diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java index 84486a391d8..4468117691b 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/domain/SearchQueryRenderer.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -131,17 +131,21 @@ public class SearchQueryRenderer implements SearchQueryVisitor { // Enable use of legacy whole-system search parameters for the search request private final boolean legacyWholeSystemSearchParamsEnabled; + // Include DATA in the data fetch queries + private final boolean includeResourceData; /** * Public constructor * @param identityCache * @param rowOffset * @param rowsPerPage + * @param includeResourceData */ public SearchQueryRenderer(JDBCIdentityCache identityCache, - int rowOffset, int rowsPerPage) { + int rowOffset, int rowsPerPage, boolean includeResourceData) { this.identityCache = identityCache; this.rowOffset = rowOffset; this.rowsPerPage = rowsPerPage; + this.includeResourceData = includeResourceData; this.legacyWholeSystemSearchParamsEnabled = FHIRConfigHelper.getBooleanProperty(PROPERTY_SEARCH_ENABLE_LEGACY_WHOLE_SYSTEM_SEARCH_PARAMS, false); } @@ -352,7 +356,7 @@ public QueryData joinResources(QueryData queryData, boolean includeResourceTypeI final String xxResources = resourceResources(queryData.getResourceType()); final String lrAliasName = "LR"; SelectAdapter select = Select.select("R.RESOURCE_ID", "R.LOGICAL_RESOURCE_ID", "R.VERSION_ID", "R.LAST_UPDATED", - "R.IS_DELETED", "R.DATA", "LR.LOGICAL_ID", "R.RESOURCE_PAYLOAD_KEY"); + "R.IS_DELETED", getDataCol(), "LR.LOGICAL_ID", "R.RESOURCE_PAYLOAD_KEY"); // Resource type id is used for whole-system-search cases where the query // can return resources of different types (e.g. both Patient and Observation) @@ -401,7 +405,7 @@ public QueryData wrapInclude(QueryData query) { final String rAlias = "R"; final String rTable = query.getResourceType() + "_RESOURCES"; SelectAdapter select = Select.select("LR.RESOURCE_ID", "LR.LOGICAL_RESOURCE_ID", "LR.VERSION_ID", - "LR.LAST_UPDATED", "LR.IS_DELETED", "R.DATA", "LR.LOGICAL_ID", "R.RESOURCE_PAYLOAD_KEY"); + "LR.LAST_UPDATED", "LR.IS_DELETED", getDataCol(), "LR.LOGICAL_ID", "R.RESOURCE_PAYLOAD_KEY"); select.from(query.getQuery().build(), alias(lrAlias)) .innerJoin(rTable, alias(rAlias), on(lrAlias, "RESOURCE_ID").eq(rAlias, "RESOURCE_ID")); return new QueryData(select, lrAlias, null, query.getResourceType(), 0); @@ -2727,4 +2731,14 @@ private List getValueAttributeNames(Type type) throws FHIRPersistenceExc private boolean isWholeSystemSearch(String resourceType) { return Resource.class.getSimpleName().equals(resourceType); } + + /** + * Get the select column entry for the resource data column. If + * the includeResourceData flag is false, the column is replaced + * with a literal NULL. + * @return + */ + private String getDataCol() { + return this.includeResourceData ? "R.DATA" : "CAST(NULL AS BLOB) AS DATA"; + } } \ No newline at end of file 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 1719eecf4df..d291a1fd840 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 @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.function.Function; @@ -95,6 +96,7 @@ import com.ibm.fhir.persistence.ResourceChangeLogRecord; import com.ibm.fhir.persistence.ResourceEraseRecord; import com.ibm.fhir.persistence.ResourcePayload; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.context.FHIRHistoryContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; @@ -672,13 +674,12 @@ public SingleResourceResult updateWithMeta(FHIRPersisten * @throws FHIRPersistenceException */ @Override - public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) + public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { final String METHODNAME = "search"; log.entering(CLASSNAME, METHODNAME); - List resources = Collections.emptyList(); - MultiResourceResult.Builder resultBuilder = new MultiResourceResult.Builder<>(); + MultiResourceResult.Builder resultBuilder = MultiResourceResult.builder(); FHIRSearchContext searchContext = context.getSearchContext(); NewQueryBuilder queryBuilder; Integer searchResultCount = null; @@ -693,6 +694,7 @@ public MultiResourceResult search(FHIRPersistenceContext context, Clas ParameterDAO parameterDao = makeParameterDAO(connection); ResourceReferenceDAO rrd = makeResourceReferenceDAO(connection); JDBCIdentityCache identityCache = new JDBCIdentityCacheImpl(cache, resourceDao, parameterDao, rrd); + List> resourceResults = null; checkModifiers(searchContext, isSystemLevelSearch(resourceType)); queryBuilder = new NewQueryBuilder(connectionStrategy.getQueryHints(), identityCache); @@ -770,28 +772,29 @@ public MultiResourceResult search(FHIRPersistenceContext context, Clas resourceDTOList = resourceDao.search(wholeSystemDataQuery); } } else if (searchContext.hasSortParameters()) { - resourceDTOList = this.buildSortedResourceDTOList(resourceDao, resourceType, resourceDao.searchForIds(query)); + resourceDTOList = this.buildSortedResourceDTOList(resourceDao, resourceType, resourceDao.searchForIds(query), searchContext.isIncludeResourceData()); } else { resourceDTOList = resourceDao.search(query); } - resources = this.convertResourceDTOList(resourceDao, resourceDTOList, resourceType, elements); - searchContext.setMatchCount(resources.size()); + resourceResults = this.convertResourceDTOList(resourceDao, resourceDTOList, resourceType, elements, searchContext.isIncludeResourceData()); + searchContext.setMatchCount(resourceResults.size()); // Check if _include or _revinclude search. If so, generate queries for each _include or // _revinclude parameter and add the returned 'include' resources to the 'match' resource // list. All duplicates in the 'include' resources (duplicates of both 'match' and 'include' // resources) will be removed and _elements processing will not be done for 'include' resources. - if (resources.size() > 0 && (searchContext.hasIncludeParameters() || searchContext.hasRevIncludeParameters())) { + if (resourceResults.size() > 0 && (searchContext.hasIncludeParameters() || searchContext.hasRevIncludeParameters())) { List includeDTOList = newSearchForIncludeResources(searchContext, resourceType, queryBuilder, resourceDao, resourceDTOList); - resources.addAll(this.convertResourceDTOList(resourceDao, includeDTOList, resourceType, null)); + List> includeResult = this.convertResourceDTOList(resourceDao, includeDTOList, resourceType, null, searchContext.isIncludeResourceData()); + resourceResults.addAll(includeResult); } } return resultBuilder .success(true) - .resource(resources) + .addResourceResults(resourceResults) .build(); } catch (FHIRPersistenceException e) { throw e; @@ -1260,15 +1263,14 @@ public SingleResourceResult read(FHIRPersistenceContext } @Override - public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, + public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { final String METHODNAME = "history"; log.entering(CLASSNAME, METHODNAME); - List resources = new ArrayList<>(); - MultiResourceResult.Builder resultBuilder = new MultiResourceResult.Builder<>(); + MultiResourceResult.Builder resultBuilder = MultiResourceResult.builder(); + List> resourceResults = new ArrayList<>(); List resourceDTOList; - Map> deletedResourceVersions = new HashMap<>(); FHIRHistoryContext historyContext; int resourceCount; Instant since; @@ -1280,7 +1282,6 @@ public MultiResourceResult history(FHIRPersistenceContex ResourceDAO resourceDao = makeResourceDAO(connection); historyContext = context.getHistoryContext(); - historyContext.setDeletedResources(deletedResourceVersions); since = historyContext.getSince(); if (since != null) { fromDateTime = FHIRUtilities.convertToTimestamp(since.getValue()); @@ -1303,19 +1304,12 @@ public MultiResourceResult history(FHIRPersistenceContex if (resourceCount > 0) { offset = (historyContext.getPageNumber() - 1) * historyContext.getPageSize(); resourceDTOList = resourceDao.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()); - deletedResourceVersions.get(logicalId).add(resourceDTO.getVersionId()); - } - } - log.log(Level.FINE, "deletedResourceVersions=" + deletedResourceVersions); - resources = this.convertResourceDTOList(resourceDTOList, resourceType); + resourceResults = this.convertResourceDTOList(resourceDao, resourceDTOList, resourceType, null, true); } return resultBuilder .success(true) - .resource(resources) + .addResourceResults(resourceResults) .build(); } catch(FHIRPersistenceException e) { @@ -1480,12 +1474,14 @@ public SingleResourceResult vread(FHIRPersistenceContext * @param resourceDao - The resource DAO. * @param resourceType - The type of Resource that each id in the passed list represents. * @param sortedIdList - A list of Resource ids representing the proper sort order for the list of Resources to be returned. + * @param includeResourceData include the resource DATA value * @return List - A list of ResourcesDTOs of the passed resourceType, * sorted according the order of ids in the passed sortedIdList. * @throws FHIRPersistenceException * @throws IOException */ - protected List buildSortedResourceDTOList(ResourceDAO resourceDao, Class resourceType, List sortedIdList) + protected List buildSortedResourceDTOList(ResourceDAO resourceDao, Class resourceType, List sortedIdList, + boolean includeResourceData) throws FHIRException, FHIRPersistenceException, IOException { final String METHOD_NAME = "buildSortedResourceDTOList"; log.entering(this.getClass().getName(), METHOD_NAME); @@ -1503,7 +1499,7 @@ protected List buildSortedResourceDT idPositionMap.put(resourceId, i); } - resourceDTOList = this.getResourceDTOs(resourceDao, resourceType, sortedIdList); + resourceDTOList = this.getResourceDTOs(resourceDao, resourceType, sortedIdList, includeResourceData); // Store each ResourceDTO in its proper position in the returned sorted list. for (com.ibm.fhir.persistence.jdbc.dto.Resource resourceDTO : resourceDTOList) { @@ -1526,36 +1522,43 @@ protected List buildSortedResourceDT * @param resourceDao - The resource DAO. * @param resourceType The type of resource being queried. * @param sortedIdList A sorted list of Resource IDs. + * @param includeResourceData Include the resource DATA value * @return List - A list of ResourceDTOs * @throws FHIRPersistenceDataAccessException * @throws FHIRPersistenceDBConnectException */ private List getResourceDTOs(ResourceDAO resourceDao, - Class resourceType, List sortedIdList) throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException { + Class resourceType, List sortedIdList, boolean includeResourceData) + throws FHIRPersistenceDataAccessException, FHIRPersistenceDBConnectException { - return resourceDao.searchByIds(resourceType.getSimpleName(), sortedIdList); + return resourceDao.searchByIds(resourceType.getSimpleName(), sortedIdList, includeResourceData); } /** * Converts the passed Resource Data Transfer Object collection to a collection of FHIR Resource objects. + * @param resourceDao * @param resourceDTOList * @param resourceType + * @param elements + * @param includeResourceData * @return * @throws FHIRException * @throws IOException */ - protected List convertResourceDTOList(ResourceDAO resourceDao, List resourceDTOList, - Class resourceType, List elements) throws FHIRException, IOException { + protected List> convertResourceDTOList(ResourceDAO resourceDao, List resourceDTOList, + Class resourceType, List elements, boolean includeResourceData) throws FHIRException, IOException { final String METHODNAME = "convertResourceDTO List"; log.entering(CLASSNAME, METHODNAME); - List resources = new ArrayList<>(); + List> resourceResults = new ArrayList<>(resourceDTOList.size()); try { for (com.ibm.fhir.persistence.jdbc.dto.Resource resourceDTO : resourceDTOList) { // TODO Linear fetch of a large number of resources will extend response times. Need // to look into batch or parallel fetch requests - Resource existingResource = convertResourceDTO(resourceDTO, resourceType, elements); - if (existingResource == null) { + ResourceResult resourceResult = convertResourceDTOToResourceResult(resourceDTO, resourceType, elements, includeResourceData); + + // Check to make sure we got a Resource if we asked for it + if (resourceResult.getResource() == null && includeResourceData) { String resourceTypeName = getResourceTypeInfo(resourceDTO); if (resourceTypeName == null) { resourceTypeName = resourceType.getSimpleName(); @@ -1563,17 +1566,16 @@ protected List convertResourceDTOList(ResourceDAO resourceDao, List List convertResourceDTOList(List T convertResourceDTO(com.ibm.fhir.persistence.jdbc.dto.Resource resourceDTO, Class resourceType, List elements) throws FHIRException, IOException { final String METHODNAME = "convertResourceDTO"; @@ -2247,6 +2250,78 @@ private T convertResourceDTO(com.ibm.fhir.persistence.jdbc. return result; } + private ResourceResult convertResourceDTOToResourceResult(com.ibm.fhir.persistence.jdbc.dto.Resource resourceDTO, + Class resourceType, List elements, boolean includeData) throws FHIRException, IOException { + final String METHODNAME = "convertResourceDTO"; + log.entering(CLASSNAME, METHODNAME); + Objects.requireNonNull(resourceDTO, "resourceDTO must be not null"); + T resource; + + if (includeData) { + if (this.payloadPersistence != null) { + // The payload needs to be read from the FHIRPayloadPersistence impl. If this is + // a form of whole-system query (search or history), then the resource type needs + // to come from the DTO itself + String rowResourceTypeName = getResourceTypeInfo(resourceDTO); + int resourceTypeId; + if (rowResourceTypeName != null) { + resourceTypeId = getResourceTypeId(rowResourceTypeName); + } else { + rowResourceTypeName = resourceType.getSimpleName(); + resourceTypeId = getResourceTypeId(resourceType); + } + + // If a specific version of a resource has been deleted using $erase, it + // is possible for the result here to be null. + resource = payloadPersistence.readResource(resourceType, rowResourceTypeName, resourceTypeId, resourceDTO.getLogicalId(), resourceDTO.getVersionId(), resourceDTO.getResourcePayloadKey(), elements); + } else { + // original impl - the resource, if any, was read from the RDBMS + if (resourceDTO.getDataStream() != null) { + try (InputStream in = new GZIPInputStream(resourceDTO.getDataStream().inputStream())) { + FHIRParser parser = FHIRParser.parser(Format.JSON); + parser.setValidating(false); + if (elements != null) { + // parse/filter the resource using elements + resource = parser.as(FHIRJsonParser.class).parseAndFilter(in, elements); + if (resourceType.equals(resource.getClass()) && !FHIRUtil.hasTag(resource, SearchConstants.SUBSETTED_TAG)) { + // add a SUBSETTED tag to this resource to indicate that its elements have been filtered + resource = FHIRUtil.addTag(resource, SearchConstants.SUBSETTED_TAG); + } + } else { + resource = parser.parse(in); + } + } + } else { + // Queries may return a NULL for the DATA column if the resource has been erased + // or the query was asked not to fetch DATA in the first place + resource = null; + } + } + } else { + resource = null; + } + + // Note that resource may be null. We return a ResourceResult so we can + // communicate back the type/id/version information even if we didn't get + // an actual resource object + String resourceTypeName = getResourceTypeInfo(resourceDTO); + if (resourceTypeName == null) { + // By default we simply use the requested type name. This makes the ResourceResult + // easier to consume by the caller + resourceTypeName = resourceType.getSimpleName(); + } + ResourceResult.Builder builder = new ResourceResult.Builder<>(); + builder.logicalId(resourceDTO.getLogicalId()); + builder.resourceTypeName(resourceTypeName); + builder.deleted(resourceDTO.isDeleted()); + builder.resource(resource); // can be null + builder.version(resourceDTO.getVersionId()); + builder.lastUpdated(resourceDTO.getLastUpdated().toInstant()); + + log.exiting(CLASSNAME, METHODNAME); + return builder.build(); + } + /** * Get the resource type name of the resource represented by the from the * given resourceDTO. This is only done if the resourceTypeId field in the diff --git a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/NewQueryBuilder.java b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/NewQueryBuilder.java index 1483ac1ef09..97c2be6c833 100644 --- a/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/NewQueryBuilder.java +++ b/fhir-persistence-jdbc/src/main/java/com/ibm/fhir/persistence/jdbc/util/NewQueryBuilder.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -219,7 +219,7 @@ public Select buildCountQuery(Class resourceType, FHIRSearchContext searchCon private Select renderQuery(SearchQuery domainModel, FHIRSearchContext searchContext) throws FHIRPersistenceException { final int offset = (searchContext.getPageNumber()-1) * searchContext.getPageSize(); final int rowsPerPage = searchContext.getPageSize(); - SearchQueryRenderer renderer = new SearchQueryRenderer(this.identityCache, offset, rowsPerPage); + SearchQueryRenderer renderer = new SearchQueryRenderer(this.identityCache, offset, rowsPerPage, searchContext.isIncludeResourceData()); QueryData queryData = domainModel.visit(renderer); return queryData.getQuery().build(); } diff --git a/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/search/test/JDBCSearchNearTest.java b/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/search/test/JDBCSearchNearTest.java index f329dafe30c..7ab9954ac88 100644 --- a/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/search/test/JDBCSearchNearTest.java +++ b/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/search/test/JDBCSearchNearTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,7 +34,6 @@ import com.ibm.fhir.database.utils.api.IConnectionProvider; import com.ibm.fhir.database.utils.pool.PoolConnectionProvider; import com.ibm.fhir.model.resource.Location; -import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.test.TestUtil; import com.ibm.fhir.model.type.Id; import com.ibm.fhir.model.type.Instant; @@ -125,7 +124,7 @@ public void teardown() throws Exception { persistence.getTransaction().begin(); } - FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, Collections.emptyMap(), true); + FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, Collections.emptyMap(), true, true); FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(null, ctx); persistence.delete(persistenceContext, Location.class, savedResource.getId()); @@ -136,7 +135,7 @@ public void teardown() throws Exception { FHIRRequestContext.get().setTenantId("default"); } - public MultiResourceResult runQueryTest(String searchParamCode, String queryValue) throws Exception { + public MultiResourceResult runQueryTest(String searchParamCode, String queryValue) throws Exception { Map> queryParms = new HashMap>(1); if (searchParamCode != null && queryValue != null) { queryParms.put(searchParamCode, Collections.singletonList(queryValue)); @@ -144,7 +143,7 @@ public MultiResourceResult runQueryTest(String searchParamCode, String return runQueryTest(queryParms); } - public MultiResourceResult runQueryTestMultiples(String searchParamCode, String... queryValues) + public MultiResourceResult runQueryTestMultiples(String searchParamCode, String... queryValues) throws Exception { Map> queryParms = new LinkedHashMap>(queryValues.length); for (String queryValue : queryValues) { @@ -156,10 +155,10 @@ public MultiResourceResult runQueryTestMultiples(String searchParamCod return runQueryTest(queryParms); } - public MultiResourceResult runQueryTest(Map> queryParms) throws Exception { - FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, queryParms, true); + public MultiResourceResult runQueryTest(Map> queryParms) throws Exception { + FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, queryParms, true, true); FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(null, ctx); - MultiResourceResult result = persistence.search(persistenceContext, Location.class); + MultiResourceResult result = persistence.search(persistenceContext, Location.class); return result; } @@ -183,9 +182,9 @@ public void testSearchPositionSearchExactSmallRangeMatch() throws Exception { String searchParamCode = "near"; String queryValue = "42.25475478|-83.6945691|10.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertFalse(result.getResource().size() == 0); + assertFalse(result.getResourceResults().size() == 0); assertNull(result.getOutcome()); } @@ -195,9 +194,9 @@ public void testSearchPositionSearchExactLargeRangeMatch() throws Exception { String searchParamCode = "near"; String queryValue = "42.25475478|0|10000.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertNotEquals(result.getResource().size(), 0); + assertNotEquals(result.getResourceResults().size(), 0); assertNull(result.getOutcome()); } @@ -207,9 +206,9 @@ public void testSearchPositionSearchExactMatchWithinSmallRange() throws Exceptio String searchParamCode = "near"; String queryValue = "42.0|-83.0|500.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertNotEquals(result.getResource().size(), 0); + assertNotEquals(result.getResourceResults().size(), 0); assertNull(result.getOutcome()); } @@ -219,9 +218,9 @@ public void testSearchPositionSearchExactMatchNotMatchingRange() throws Exceptio String searchParamCode = "near"; String queryValue = "-83.0|42.0|1.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test @@ -230,9 +229,9 @@ public void testSearchPositionSearchExactMatchWithinRangeNot() throws Exception String searchParamCode = "near"; String queryValue = "-79|40|523.3|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test @@ -243,9 +242,9 @@ public void testSearchPositionSearchExactMatchWithinRange() throws Exception { String searchParamCode = "near"; String queryValue = "40|-79|1046.6|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertNotEquals(result.getResource().size(), 0); + assertNotEquals(result.getResourceResults().size(), 0); } @Test @@ -254,9 +253,9 @@ public void testSearchPositionSearchExactMatch() throws Exception { String searchParamCode = "near"; String queryValue = "42.25475478|-83.6945691|0.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertNotEquals(result.getResource().size(), 0); + assertNotEquals(result.getResourceResults().size(), 0); assertNull(result.getOutcome()); } @@ -267,9 +266,9 @@ public void testSearchPositionSearchExactMatchMultiples() throws Exception { String queryValue1 = "42.25475478|-83.6945691|0.0|km"; String queryValue2 = "42.25475478|-83.6945691|0.0|km"; - MultiResourceResult result = runQueryTestMultiples(searchParamCode, queryValue1, queryValue2); + MultiResourceResult result = runQueryTestMultiples(searchParamCode, queryValue1, queryValue2); assertNotNull(result); - assertNotEquals(result.getResource().size(), 0); + assertNotEquals(result.getResourceResults().size(), 0); assertNull(result.getOutcome()); } @@ -279,9 +278,9 @@ public void testSearchPositionSearchExactMatchNotMatching() throws Exception { String searchParamCode = "near"; String queryValue = "83.6945691|-42.25475478|0.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertEquals(result.getResource().size(), 0); + assertEquals(result.getResourceResults().size(), 0); } @Test @@ -290,9 +289,9 @@ public void testSearchPositionSearchExactMatchUnitMiles() throws Exception { String searchParamCode = "near"; String queryValue = "42.25475478|-83.6945691|0.0|mi_us"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertFalse(result.getResource().size() == 0); + assertFalse(result.getResourceResults().size() == 0); assertNull(result.getOutcome()); } @@ -302,9 +301,9 @@ public void testSearchPositionSearchBadPrefix() throws Exception { String searchParamCode = "near"; String queryValue = "ap83.6945691|-42.25475478|0.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test(expectedExceptions = { FHIRPersistenceException.class }) @@ -313,9 +312,9 @@ public void testSearchPositionSearchBadInputLon() throws Exception { String searchParamCode = "near"; String queryValue = "-42.25475478|FUDGESHOULDNOTMATCH|0.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test(expectedExceptions = { FHIRPersistenceException.class }) @@ -324,9 +323,9 @@ public void testSearchPositionSearchBadInputLat() throws Exception { String searchParamCode = "near"; String queryValue = "FUDGESHOULDNOTMATCH|-42.25475478|0.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test(expectedExceptions = { FHIRPersistenceException.class }) @@ -335,9 +334,9 @@ public void testSearchPositionSearchBadInputRadius() throws Exception { String searchParamCode = "near"; String queryValue = "-42.25475478|-42.25475478|FUDGESHOULDNOTMATCH|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test(expectedExceptions = { FHIRPersistenceException.class }) @@ -346,9 +345,9 @@ public void testSearchPositionSearchBadInputUnit() throws Exception { String searchParamCode = "near"; String queryValue = "-42.25475478|-42.25475478|0.0|FUDGESHOULDNOTMATCH"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertTrue(result.getResource().size() == 0); + assertTrue(result.getResourceResults().size() == 0); } @Test @@ -357,9 +356,9 @@ public void testSearchPositionSearchExactMatchGoodPrefix() throws Exception { String searchParamCode = "near"; String queryValue = "eq42.25475478|-83.6945691|0.0|km"; - MultiResourceResult result = runQueryTest(searchParamCode, queryValue); + MultiResourceResult result = runQueryTest(searchParamCode, queryValue); assertNotNull(result); - assertFalse(result.getResource().size() == 0); + assertFalse(result.getResourceResults().size() == 0); assertNull(result.getOutcome()); } } \ No newline at end of file diff --git a/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/spec/HistoryOperation.java b/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/spec/HistoryOperation.java index 9829d218483..51e9d4e7957 100644 --- a/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/spec/HistoryOperation.java +++ b/fhir-persistence-jdbc/src/test/java/com/ibm/fhir/persistence/jdbc/test/spec/HistoryOperation.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,14 +9,22 @@ import java.util.List; import com.ibm.fhir.model.resource.Resource; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; +/** + * Test the history interaction for a resource + */ public class HistoryOperation extends BaseOperation { // the number of resource versions we expect to read from the database final int expectedCount; - + + /** + * Public constructor + * @param expectedCount + */ public HistoryOperation(int expectedCount) { this.expectedCount = expectedCount; } @@ -30,7 +38,7 @@ public void process(TestContext tc) throws FHIRPersistenceException { final String logicalId = resource.getId(); - List resources = tc.getPersistence().history(context, resource.getClass(), logicalId).getResource(); + List> resources = tc.getPersistence().history(context, resource.getClass(), logicalId).getResourceResults(); if (resources.size() != this.expectedCount) { throw new AssertionError(resource.getClass().getSimpleName() + "/" + logicalId + " history returned " + resources.size() + ", expected " + this.expectedCount); diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/FHIRPersistence.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/FHIRPersistence.java index a1b4e519a00..455e6684afe 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/FHIRPersistence.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/FHIRPersistence.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -148,7 +148,7 @@ default void deleteWithMeta(FHIRPersistenceContext context, * an OperationOutcome with hints, warnings, or errors related to the interaction * @throws FHIRPersistenceException */ - MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException; + MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException; /** * Performs a search on the specified target resource type using the specified search parameters. @@ -159,7 +159,7 @@ default void deleteWithMeta(FHIRPersistenceContext context, * an OperationOutcome with hints, warnings, or errors related to the interaction * @throws FHIRPersistenceException */ - MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException; + MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException; /** * Returns true iff the persistence layer implementation supports transactions. diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/MultiResourceResult.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/MultiResourceResult.java index cbd97864f19..21a6121fb33 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/MultiResourceResult.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/MultiResourceResult.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,7 +7,6 @@ package com.ibm.fhir.persistence; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -20,15 +19,15 @@ * A Result wrapper for FHIR interactions that can return multiple resources. * Instances are immutable and can be constructed via {@code new MultiResourceResult.Builder()}. */ -public class MultiResourceResult { +public class MultiResourceResult { @Required final boolean success; - final List resource; + final List> resourceResults; final OperationOutcome outcome; - private MultiResourceResult(Builder builder) { + private MultiResourceResult(Builder builder) { success = ValidationSupport.requireNonNull(builder.success, "success"); - resource = Collections.unmodifiableList(builder.resource); + resourceResults = Collections.unmodifiableList(builder.resourceResults); outcome = builder.outcome; if (!success && (outcome == null || outcome.getIssue().isEmpty())) { throw new IllegalStateException("Failed interaction results must include an OperationOutcome with one or more issue."); @@ -44,15 +43,16 @@ private MultiResourceResult(Builder builder) { public boolean isSuccess() { return success; } + /** - * The resources returned from the interaction - * + * The resource results returned from the interaction * @return - * An unmodifiable list containing immutable objects of type {@link Resource}. + * An unmodifiable list containing immutable objects of type {@link ResourceResult} */ - public List getResource() { - return resource; + public List> getResourceResults() { + return this.resourceResults; } + /** * An OperationOutcome that represents the outcome of the interaction * @@ -63,14 +63,14 @@ public OperationOutcome getOutcome() { return outcome; } - public static Builder builder(Class clazz) { - return new Builder(); + public static Builder builder() { + return new Builder(); } // result builder - public static class Builder { + public static class Builder { boolean success; - List resource = new ArrayList<>(); + final List> resourceResults = new ArrayList<>(); OperationOutcome outcome; /** @@ -84,46 +84,38 @@ public static class Builder { * @return * A reference to this Builder instance */ - public Builder success(boolean success) { + public Builder success(boolean success) { this.success = success; return this; } + /** - * The return resources from the interaction - * - *

Adds new element(s) to the existing list - * - * @param resource - * the resources to return from the interaction; this may be empty if there are no results - * + * Add the resource results to the resourceResults list + * @param resourceResultsParam * @return - * A reference to this Builder instance */ @SafeVarargs - public final Builder resource(T... resource) { - for (T value : resource) { - this.resource.add(value); + public final Builder resourceResult(ResourceResult... resourceResultsParam) { + for (ResourceResult value : resourceResultsParam) { + this.resourceResults.add(value); } return this; } - + /** - * The return resources from the interaction - * - *

Replaces the existing list with a new one containing elements from the Collection - * - * @param resource - * the resources to return from the interaction; this may be empty if there are no results - * + * Add the resource result list to resource list owned by this + * @param resourceResultsParam * @return - * A reference to this Builder instance */ - public Builder resource(Collection resource) { - this.resource = new ArrayList<>(resource); + public final Builder addResourceResults(List> resourceResultsList) { + if (resourceResultsList != null) { + this.resourceResults.addAll(resourceResultsList); + } return this; } + /** * An OperationOutcome that represents the outcome of the interaction * @@ -135,7 +127,7 @@ public Builder resource(Collection resource) { * @return * A reference to this Builder instance */ - public Builder outcome(OperationOutcome outcome) { + public Builder outcome(OperationOutcome outcome) { this.outcome = outcome; return this; } @@ -151,8 +143,8 @@ public Builder outcome(OperationOutcome outcome) { * @return * An immutable object of type {@link MultiResourceResult} */ - public MultiResourceResult build() { - return new MultiResourceResult(this); + public MultiResourceResult build() { + return new MultiResourceResult(this); } } } \ No newline at end of file diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/ResourceResult.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/ResourceResult.java new file mode 100644 index 00000000000..91c7ae4af31 --- /dev/null +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/ResourceResult.java @@ -0,0 +1,245 @@ +/* + * (C) Copyright IBM Corp. 2021, 2022 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.persistence; + +import java.time.Instant; +import java.util.List; +import java.util.stream.Collectors; + +import com.ibm.fhir.model.annotation.Required; +import com.ibm.fhir.model.resource.Resource; + +/** + * The base result wrapper used to represent a resource being returned from a + * persistence interaction. + * Instances are immutable and can be constructed via {@code new ResourceResult.Builder()}. + */ +public class ResourceResult { + @Required + private final T resource; + private final boolean deleted; + private final String resourceTypeName; + private final String logicalId; + private final int version; + private final Instant lastUpdated; + + /** + * Private constructor used by the Builder to create a new ResourceResult instance + * @param builder + */ + private ResourceResult(Builder builder) { + resource = builder.resource; + deleted = builder.deleted; + resourceTypeName = builder.resourceTypeName; + logicalId = builder.logicalId; + version = builder.version; + lastUpdated = builder.lastUpdated; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(resourceTypeName); + result.append("/"); + result.append(logicalId); + result.append("/_history/"); + result.append(version); + + if (deleted) { + result.append(" [deleted]"); + } + return result.toString(); + } + + /** + * Create a Builder for building instances of this class + * @return + */ + public static ResourceResult.Builder builder() { + return new ResourceResult.Builder<>(); + } + + /** + * Convenience method to convert a Resource value to a ResourceResult, + * which is useful for unit-tests + * + * @param + * @param resource with a valid meta field + * @return + */ + public static ResourceResult from(Resource resource) { + return ResourceResult.builder() + .resource(resource) + .lastUpdated(resource.getMeta().getLastUpdated().getValue().toInstant()) + .version(Integer.parseInt(resource.getMeta().getVersionId().getValue())) + .logicalId(resource.getId()) + .resourceTypeName(resource.getClass().getSimpleName()) + .build(); + } + + /** + * Convenience function to convert a list of ResourceResults to a list of Resources + * which are not null + * @param resourceResults + * @return + */ + public static List toResourceList(List> resourceResults) { + return resourceResults.stream().filter(rr -> rr.getResource() != null).map(rr -> rr.getResource()).collect(Collectors.toList()); + } + + /** + * Whether or not the resource is deleted + * @return whether the resource is deleted + */ + public boolean isDeleted() { + return deleted; + } + + /** + * The resource resulting from the interaction + * + * @return + * An immutable object of type {@link Resource}. + */ + public T getResource() { + return resource; + } + + /** + * @return the resourceTypeName + */ + public String getResourceTypeName() { + return resourceTypeName; + } + + /** + * @return the logicalId + */ + public String getLogicalId() { + return logicalId; + } + + /** + * @return the version + */ + public int getVersion() { + return version; + } + + /** + * @return the lastUpdated + */ + public Instant getLastUpdated() { + return lastUpdated; + } + + // result builder + public static class Builder { + private T resource; + private boolean deleted; + private String resourceTypeName; + private String logicalId; + private int version; + private Instant lastUpdated; + + /** + * Whether or not the resource is deleted + * + * @param flag + * @return A reference to this Builder instance + */ + public Builder deleted(boolean flag) { + this.deleted = flag; + return this; + } + + /** + * The resulting resource from the interaction + * + * @param resource + * the resulting resource from the interaction; this may be null if the interaction was not successful + * + * @return + * A reference to this Builder instance + */ + public Builder resource(T resource) { + this.resource = resource; + return this; + } + + /** + * The lastUpdated time of the resource + * + * @param lastUpdated + * the lastUpdated timestamp representing the UTC modification time of the resource + * + * @return + * A reference to this Builder instance + */ + public Builder lastUpdated(Instant lastUpdated) { + this.lastUpdated = lastUpdated; + return this; + } + + /** + * The type name of the resource which should be set when the resource + * value itself is null + * + * @param resourceTypeName + * The type name of the resource this result represents + * @return + * A reference to this Builder instance + */ + public Builder resourceTypeName(String resourceTypeName) { + this.resourceTypeName = resourceTypeName; + return this; + } + + /** + * Sets the logicalId of the resource which should be set when the resource + * value itself is null + * + * @param logicalId + * The logicalId of this resource + * @return + * A reference to this Builder instance + */ + public Builder logicalId(String logicalId) { + this.logicalId = logicalId; + return this; + } + + /** + * Sets the version of the resource which should be set when the resource + * value itself is null + * + * @param version + * The version of this resource + * @return + * A reference to this Builder instance + */ + public Builder version(int version) { + this.version = version; + return this; + } + + /** + * Build the {@link SingleResourceResult} + * + *

Required fields: + *

    + *
  • success
  • + *
+ * + * @return + * An immutable object of type {@link SingleResourceResult} + */ + public ResourceResult build() { + return new ResourceResult(this); + } + } +} \ No newline at end of file diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/SingleResourceResult.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/SingleResourceResult.java index 0c3cec35966..7fc867c0058 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/SingleResourceResult.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/SingleResourceResult.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,20 +19,18 @@ */ public class SingleResourceResult { @Required - final boolean success; - final T resource; - final OperationOutcome outcome; - final boolean deleted; - final InteractionStatus interactionStatus; + private final ResourceResult resourceResult; + private final boolean success; + private final OperationOutcome outcome; + private final InteractionStatus interactionStatus; // The current version of the resource returned by the database if we hit IfNoneMatch final Integer ifNoneMatchVersion; private SingleResourceResult(Builder builder) { success = ValidationSupport.requireNonNull(builder.success, "success"); - resource = builder.resource; + resourceResult = builder.resourceResultBuilder.build(); outcome = builder.outcome; - deleted = builder.deleted; interactionStatus = builder.interactionStatus; ifNoneMatchVersion = builder.ifNoneMatchVersion; @@ -64,7 +62,7 @@ public boolean isSuccess() { * @return whether the resource is deleted */ public boolean isDeleted() { - return deleted; + return resourceResult.isDeleted(); } /** @@ -74,7 +72,7 @@ public boolean isDeleted() { * An immutable object of type {@link Resource}. */ public T getResource() { - return resource; + return resourceResult.getResource(); } /** @@ -103,14 +101,34 @@ public OperationOutcome getOutcome() { return outcome; } + /** + * @return the type name of the resource + */ + public String getResourceTypeName() { + return resourceResult.getResourceTypeName(); + } + + /** + * @return the logicalId of the resource + */ + public String getLogicalId() { + return resourceResult.getLogicalId(); + } + + /** + * @return the version of the resource + */ + public int getVersion() { + return resourceResult.getVersion(); + } + // result builder public static class Builder { private boolean success; - private T resource; private OperationOutcome outcome; - private boolean deleted; private InteractionStatus interactionStatus; private Integer ifNoneMatchVersion; + private final ResourceResult.Builder resourceResultBuilder = new ResourceResult.Builder<>(); /** * Whether or not the interaction was successful @@ -157,7 +175,7 @@ public Builder ifNoneMatchVersion(Integer versionId) { * @return A reference to this Builder instance */ public Builder deleted(boolean flag) { - this.deleted = flag; + resourceResultBuilder.deleted(flag); return this; } @@ -171,7 +189,7 @@ public Builder deleted(boolean flag) { * A reference to this Builder instance */ public Builder resource(T resource) { - this.resource = resource; + resourceResultBuilder.resource(resource); return this; } @@ -191,6 +209,48 @@ public Builder outcome(OperationOutcome outcome) { return this; } + /** + * The type name of the resource which should be set when the resource + * value itself is null + * + * @param resourceTypeName + * The type name of the resource this result represents + * @return + * A reference to this Builder instance + */ + public Builder resourceTypeName(String resourceTypeName) { + resourceResultBuilder.resourceTypeName(resourceTypeName); + return this; + } + + /** + * Sets the logicalId of the resource which should be set when the resource + * value itself is null + * + * @param logicalId + * The logicalId of this resource + * @return + * A reference to this Builder instance + */ + public Builder logicalId(String logicalId) { + resourceResultBuilder.logicalId(logicalId); + return this; + } + + /** + * Sets the version of the resource which should be set when the resource + * value itself is null + * + * @param version + * The version of this resource + * @return + * A reference to this Builder instance + */ + public Builder version(int version) { + resourceResultBuilder.version(version); + return this; + } + /** * Build the {@link SingleResourceResult} * diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/FHIRHistoryContext.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/FHIRHistoryContext.java index 4e6c154a7ee..28f62bc95ad 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/FHIRHistoryContext.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/FHIRHistoryContext.java @@ -1,14 +1,11 @@ /* - * (C) Copyright IBM Corp. 2016,2019 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ package com.ibm.fhir.persistence.context; -import java.util.List; -import java.util.Map; - import com.ibm.fhir.core.context.FHIRPagingContext; import com.ibm.fhir.model.type.Instant; @@ -16,20 +13,4 @@ public interface FHIRHistoryContext extends FHIRPagingContext { Instant getSince(); void setSince(Instant since); - - /** - * Returns a Map indicating the deletion history of a resource. - * The map key is the logical resource id. The value is a List of deleted versions of the resource. - * Note there can be more than one deleted version, since a deleted resource can be brought back to life by a subsequent update. - * @return deleted resources Map - */ - Map> getDeletedResources(); - - /** - * Sets a Map indicating the deletion history of a resource. - * The map key is the logical resource id. The value is a List of deleted versions of the resource. - * Note there can be more than one deleted version, since a deleted resource can be brought back to life by a subsequent update. - * @param deletedResources - */ - void setDeletedResources(Map> deletedResources); } diff --git a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/impl/FHIRHistoryContextImpl.java b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/impl/FHIRHistoryContextImpl.java index f98155952fd..b690bf1d2e3 100644 --- a/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/impl/FHIRHistoryContextImpl.java +++ b/fhir-persistence/src/main/java/com/ibm/fhir/persistence/context/impl/FHIRHistoryContextImpl.java @@ -1,22 +1,17 @@ /* - * (C) Copyright IBM Corp. 2016,2019 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ package com.ibm.fhir.persistence.context.impl; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import com.ibm.fhir.core.context.impl.FHIRPagingContextImpl; import com.ibm.fhir.model.type.Instant; import com.ibm.fhir.persistence.context.FHIRHistoryContext; public class FHIRHistoryContextImpl extends FHIRPagingContextImpl implements FHIRHistoryContext { private Instant since = null; - private Map> deletedResources = new HashMap<>(); public FHIRHistoryContextImpl() { } @@ -30,16 +25,4 @@ public Instant getSince() { public void setSince(Instant since) { this.since = since; } - - @Override - public Map> getDeletedResources() { - return this.deletedResources; - - } - - @Override - public void setDeletedResources(Map> deletedResources) { - this.deletedResources = deletedResources; - - } } 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 183385fd58b..d7ede80cc29 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -25,6 +25,7 @@ import com.ibm.fhir.model.util.FHIRUtil; import com.ibm.fhir.model.util.ModelSupport; import com.ibm.fhir.persistence.HistorySortOrder; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.context.FHIRHistoryContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; import com.ibm.fhir.persistence.context.FHIRSystemHistoryContext; @@ -182,32 +183,24 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map createDeletedResourceResultMarker(String resourceType, String logicalId, int version, java.time.Instant lastUpdated) { + + return ResourceResult.builder() + .deleted(true) + .resourceTypeName(resourceType) + .logicalId(logicalId) + .version(version) + .lastUpdated(lastUpdated) + .build(); } /** diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractPLSearchTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractPLSearchTest.java index 397a884ce81..67e1c409cfc 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractPLSearchTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractPLSearchTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2018, 2021 + * (C) Copyright IBM Corp. 2018, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,9 +29,12 @@ import com.ibm.fhir.model.type.DateTime; import com.ibm.fhir.model.type.Reference; import com.ibm.fhir.model.type.code.CompositionStatus; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; import com.ibm.fhir.persistence.test.common.AbstractPersistenceTest; +import com.ibm.fhir.search.context.FHIRSearchContext; +import com.ibm.fhir.search.util.SearchUtil; /** * An abstract parent for the persistence layer search tests. @@ -175,6 +178,37 @@ protected boolean searchReturnsResource(Class resourceTypeTo return isResourceInResponse(expectedResource, resources); } + /** + * Executes the query test and returns whether the expected resource logicalId was found in + * the result set + * @param resourceTypeToSearch the resource type class to use as the base of the search + * @param queryParms + * @param expectedLogicalId + * @param includesData + * @return + * @throws Exception + */ + protected boolean searchReturnsResourceResult(Class resourceTypeToSearch, Map> queryParms, String expectedLogicalId, + boolean includesData) throws Exception { + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceTypeToSearch, queryParms); + searchContext.setIncludeResourceData(includesData); + List> resourceResults = runQueryTest( + searchContext, + resourceTypeToSearch, queryParms, Integer.MAX_VALUE).getResourceResults(); + + assertNotNull(resourceResults); + + // Find the logicalId in the output and check that the includesData matches + boolean result = false; + for (ResourceResult rr: resourceResults) { + if (rr.getLogicalId().equals(expectedLogicalId)) { + result = (rr.getResource() != null) == includesData; + break; + } + } + return result; + } + /** * Create a Composition with a reference to savedResource for chained search tests. * @throws Exception diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchIdAndLastUpdatedTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchIdAndLastUpdatedTest.java index 485e5d1c4d7..0f16664e10c 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchIdAndLastUpdatedTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchIdAndLastUpdatedTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2018, 2021 + * (C) Copyright IBM Corp. 2018, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -252,9 +252,9 @@ public void testPatientCompartmentForBulkData() throws Exception { FHIRSearchContext searchContext = SearchUtil.parseCompartmentQueryParameters("Patient", savedPatient.getId(), Observation.class, queryParms); FHIRPersistenceContext persistenceContext = getPersistenceContextForSearch(searchContext); - MultiResourceResult result = persistence.search(persistenceContext, Observation.class); - assertNotNull(result.getResource()); - assertTrue(result.getResource().size() > 0); + MultiResourceResult result = persistence.search(persistenceContext, Observation.class); + assertNotNull(result.getResourceResults()); + assertTrue(result.getResourceResults().size() > 0); } /* diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchStringTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchStringTest.java index af7e0b91d4b..633cb2cb3eb 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchStringTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/search/test/AbstractSearchStringTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2018, 2020 + * (C) Copyright IBM Corp. 2018, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -187,4 +187,31 @@ public void testSearchString_Address() throws Exception { assertSearchReturnsSavedResource("Address", "27703"); assertSearchReturnsSavedResource("Address", "USA"); } + + @Test + public void testSearchNoData() throws Exception { + Map> queryParms = new HashMap>(1); + queryParms.put("string:exact", Collections.singletonList("testString")); + + // Check that the search works when data is retrieved + assertTrue(searchReturnsResourceResult(savedResource.getClass(), queryParms, savedResource.getId(), true)); + + // And that the same search works when no data is requested + assertTrue(searchReturnsResourceResult(savedResource.getClass(), queryParms, savedResource.getId(), false)); + } + + @Test + public void testSearchSortNoData() throws Exception { + // Sort uses a second query to fetch the data, so we need to make sure + // it can also handle the no-data case + Map> queryParms = new HashMap>(1); + queryParms.put("string:exact", Collections.singletonList("testString")); + queryParms.put("_sort", Collections.singletonList("_lastUpdated")); + + // Check that the search works when data is retrieved + assertTrue(searchReturnsResourceResult(savedResource.getClass(), queryParms, savedResource.getId(), true)); + + // And that the same search works when no data is requested + assertTrue(searchReturnsResourceResult(savedResource.getClass(), queryParms, savedResource.getId(), false)); + } } diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceFactory.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceFactory.java index a73bd4ecaf3..7dfd5ea265b 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceFactory.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceFactory.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2020 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceImpl.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceImpl.java index b7ae6fbca63..199c0a7acc7 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceImpl.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/MockPersistenceImpl.java @@ -61,12 +61,12 @@ public SingleResourceResult update(FHIRPersistenceContex } @Override - public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { + public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { return null; } @Override - public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { + public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { return null; } diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractDeleteTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractDeleteTest.java index a8028a75dc5..fb4fcc9ae87 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractDeleteTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractDeleteTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,7 +14,7 @@ import java.time.ZoneOffset; import java.util.List; -import java.util.Map; +import java.util.stream.Collectors; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -23,6 +23,7 @@ import com.ibm.fhir.model.resource.Device.UdiCarrier; import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.test.TestUtil; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.context.FHIRHistoryContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; @@ -117,19 +118,16 @@ public void testSearchDeletedDevice() throws Exception { public void testHistoryDeletedDevice() throws Exception { FHIRHistoryContext historyContext = FHIRPersistenceContextFactory.createHistoryContext(); FHIRPersistenceContext context = this.getPersistenceContextForHistory(historyContext); - Map> deletedResources; - List deletedVersions; - List resources = persistence.history(context, Device.class, this.deviceId1).getResource(); + List> resources = persistence.history(context, Device.class, this.deviceId1).getResourceResults(); assertNotNull(resources); assertTrue(resources.size() == 2); - deletedResources = historyContext.getDeletedResources(); + List> deletedResources = resources.stream().filter(ResourceResult::isDeleted).collect(Collectors.toList()); + assertNotNull(deletedResources); assertTrue(deletedResources.size() == 1); - assertNotNull(deletedResources.get(this.deviceId1)); - deletedVersions = deletedResources.get(this.deviceId1); - assertEquals(1,deletedVersions.size()); - assertEquals(new Integer(2),deletedVersions.get(0)); + assertEquals(deletedResources.get(0).getLogicalId(), this.deviceId1); + assertEquals(deletedResources.get(0).getVersion(), 2); } @Test(dependsOnMethods = { "testDeleteValidDevice" }) @@ -143,8 +141,6 @@ public void testUpdateDeletedDevice() throws Exception { FHIRHistoryContext historyContext = FHIRPersistenceContextFactory.createHistoryContext(); FHIRPersistenceContext context = this.getPersistenceContextForHistory(historyContext); - Map> deletedResources; - List deletedVersions; String updatedUdiValue = "updated-udi-value"; // Read previously created device @@ -163,19 +159,16 @@ public void testUpdateDeletedDevice() throws Exception { persistence.updateWithMeta(getDefaultPersistenceContext(), device); // Verify device history - List resources = persistence.history(context, Device.class, this.deviceId2).getResource(); + List> resources = persistence.history(context, Device.class, this.deviceId2).getResourceResults(); assertNotNull(resources); assertTrue(resources.size() == 3); - deletedResources = historyContext.getDeletedResources(); - assertNotNull(deletedResources); - assertTrue(deletedResources.size() == 1); - assertNotNull(deletedResources.get(this.deviceId2)); - deletedVersions = deletedResources.get(this.deviceId2); - assertEquals(1,deletedVersions.size()); - assertEquals(new Integer(2),deletedVersions.get(0)); + List> deletedResources = resources.stream().filter(ResourceResult::isDeleted).collect(Collectors.toList()); + assertEquals(deletedResources.size(), 1); + assertEquals(deletedResources.get(0).getLogicalId(), this.deviceId2); + assertEquals(deletedResources.get(0).getVersion(), 2); // Verify latest device - Device latestDeviceVersion = resources.get(0); - assertEquals(updatedUdiValue,latestDeviceVersion.getUdiCarrier().get(0).getDeviceIdentifier().getValue()); + Device latestDeviceVersion = resources.get(0).getResource().as(Device.class); + assertEquals(latestDeviceVersion.getUdiCarrier().get(0).getDeviceIdentifier().getValue(), updatedUdiValue); } } 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 aa3c9018ea7..36f5b4d16b3 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -35,6 +35,7 @@ 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.ResourceResult; import com.ibm.fhir.persistence.context.FHIRHistoryContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; @@ -125,8 +126,8 @@ public void testSearchPaging() throws Exception { public void testHistoryPaging() throws Exception { FHIRHistoryContext historyContext; FHIRPersistenceContext context; - MultiResourceResult result; - List results; + MultiResourceResult result; + List> results; historyContext = FHIRPersistenceContextFactory.createHistoryContext(); historyContext.setPageSize(1); @@ -135,9 +136,9 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertTrue(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 1, "expected number of results"); - assertEquals(results.get(0).getMeta().getVersionId().getValue(), "3", "expected version"); + assertEquals(results.get(0).getResource().getMeta().getVersionId().getValue(), "3", "expected version"); historyContext = FHIRPersistenceContextFactory.createHistoryContext(); historyContext.setPageSize(1); @@ -146,9 +147,9 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertTrue(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 1, "expected number of results"); - assertEquals(results.get(0).getMeta().getVersionId().getValue(), "2", "expected version"); + assertEquals(results.get(0).getResource().getMeta().getVersionId().getValue(), "2", "expected version"); historyContext = FHIRPersistenceContextFactory.createHistoryContext(); @@ -158,9 +159,9 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertTrue(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 1, "expected number of results"); - assertEquals(results.get(0).getMeta().getVersionId().getValue(), "1", "expected version"); + assertEquals(results.get(0).getResource().getMeta().getVersionId().getValue(), "1", "expected version"); historyContext = FHIRPersistenceContextFactory.createHistoryContext(); historyContext.setLenient(true); @@ -170,9 +171,9 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertTrue(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 1, "expected number of results"); - assertEquals(results.get(0).getMeta().getVersionId().getValue(), "3", "expected version"); + assertEquals(results.get(0).getResource().getMeta().getVersionId().getValue(), "3", "expected version"); OperationOutcome outcome = result.getOutcome(); assertTrue(outcome != null); assertEquals(outcome.getIssue().size(), 1); @@ -187,9 +188,9 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertTrue(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 1, "expected number of results"); - assertEquals(results.get(0).getMeta().getVersionId().getValue(), "1", "expected version"); + assertEquals(results.get(0).getResource().getMeta().getVersionId().getValue(), "1", "expected version"); outcome = result.getOutcome(); assertTrue(outcome != null); assertEquals(outcome.getIssue().size(), 1); @@ -204,7 +205,7 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertFalse(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 0, "expected number of results"); outcome = result.getOutcome(); assertTrue(outcome != null); @@ -220,7 +221,7 @@ public void testHistoryPaging() throws Exception { result = persistence.history(context, resource3.getClass(), resource3.getId()); assertFalse(result.isSuccess()); - results = result.getResource(); + results = result.getResourceResults(); assertEquals(results.size(), 0, "expected number of results"); outcome = result.getOutcome(); assertTrue(outcome != null); @@ -238,9 +239,9 @@ public void testPageSizeEqualsZero() throws Exception { queryParameters.put("_count", Collections.singletonList("0")); FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); searchContext.setLenient(true); - MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); assertTrue(result.isSuccess()); - assertTrue(result.getResource().isEmpty()); + assertTrue(result.getResourceResults().isEmpty()); assertTrue(result.getOutcome() == null); assertEquals(searchContext.getLastPageNumber(), 3); } @@ -254,9 +255,9 @@ public void testInvalidPage0() throws Exception { queryParameters.put("_page", Collections.singletonList("0")); FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); searchContext.setLenient(true); - MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); assertTrue(result.isSuccess()); - assertFalse(result.getResource().isEmpty()); + assertFalse(result.getResourceResults().isEmpty()); OperationOutcome outcome = result.getOutcome(); assertTrue(outcome != null); assertEquals(outcome.getIssue().size(), 1); @@ -274,9 +275,9 @@ public void testInvalidPage0Strict() throws Exception { queryParameters.put("_page", Collections.singletonList("0")); FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); searchContext.setLenient(false); - MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); assertFalse(result.isSuccess()); - assertTrue(result.getResource().isEmpty()); + assertTrue(result.getResourceResults().isEmpty()); OperationOutcome outcome = result.getOutcome(); assertTrue(outcome != null); assertEquals(outcome.getIssue().size(), 1); @@ -294,9 +295,9 @@ public void testInvalidPage4() throws Exception { queryParameters.put("_page", Collections.singletonList("4")); FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); searchContext.setLenient(true); - MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); assertTrue(result.isSuccess()); - assertFalse(result.getResource().isEmpty()); + assertFalse(result.getResourceResults().isEmpty()); OperationOutcome outcome = result.getOutcome(); assertTrue(outcome != null); assertEquals(outcome.getIssue().size(), 1); @@ -314,9 +315,9 @@ public void testInvalidPage4Strict() throws Exception { queryParameters.put("_page", Collections.singletonList("4")); FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); searchContext.setLenient(false); - MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); assertFalse(result.isSuccess()); - assertTrue(result.getResource().isEmpty()); + assertTrue(result.getResourceResults().isEmpty()); OperationOutcome outcome = result.getOutcome(); assertTrue(outcome != null); assertEquals(outcome.getIssue().size(), 1); @@ -335,11 +336,11 @@ public void testInvalidPage4NoTotal() throws Exception { queryParameters.put("_total", Collections.singletonList("none")); FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(Basic.class, queryParameters); searchContext.setLenient(false); - MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); + MultiResourceResult result = runQueryTest(searchContext, Basic.class, queryParameters, 1); // Since _total=none, the search will not be able to determine that page 4 is too big, // but will return no resources and last page number will be left at MAX_VALUE assertTrue(result.isSuccess()); - assertTrue(result.getResource().isEmpty()); + assertTrue(result.getResourceResults().isEmpty()); OperationOutcome outcome = result.getOutcome(); assertFalse(outcome != null); assertEquals(searchContext.getLastPageNumber(), java.lang.Integer.MAX_VALUE); 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 ad64ff540ea..8ec97c6018e 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -19,6 +19,7 @@ import java.util.Set; import java.util.logging.LogManager; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterMethod; @@ -165,10 +166,12 @@ protected List runQueryTest(Class resourceType, Ma } protected List runQueryTest(Class resourceType, Map> queryParms, Integer maxPageSize) throws Exception { - return runQueryTest(SearchUtil.parseQueryParameters(resourceType, queryParms), resourceType, queryParms, maxPageSize).getResource(); + // Convert the list here so we don't have to change all the test implementations + return runQueryTest(SearchUtil.parseQueryParameters(resourceType, queryParms), resourceType, queryParms, maxPageSize).getResourceResults() + .stream().map(x -> x.getResource()).collect(Collectors.toList()); } - protected MultiResourceResult runQueryTest(FHIRSearchContext searchContext, Class resourceType, Map> queryParms, Integer maxPageSize) throws Exception { + 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()) { @@ -202,8 +205,8 @@ protected MultiResourceResult runQueryTest(FHIRSearchContext searchCon FHIRPersistenceContext persistenceContext = getPersistenceContextForSearch(searchContext); try { - MultiResourceResult result = persistence.search(persistenceContext, resourceType); - assertNotNull(result.getResource()); + MultiResourceResult result = persistence.search(persistenceContext, resourceType); + assertNotNull(result.getResourceResults()); return result; } catch (Throwable t) { debugLocks(); @@ -241,9 +244,9 @@ private List executeCompartmentQuery(Class resourc searchContext.setPageSize(maxPageSize); } FHIRPersistenceContext persistenceContext = getPersistenceContextForSearch(searchContext); - MultiResourceResult result = persistence.search(persistenceContext, resourceType); - assertNotNull(result.getResource()); - return result.getResource(); + MultiResourceResult result = persistence.search(persistenceContext, resourceType); + assertNotNull(result.getResourceResults()); + return result.getResourceResults().stream().map(x -> x.getResource()).collect(Collectors.toList()); } /** diff --git a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractSortTest.java b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractSortTest.java index 6cefe5af2e7..abd94b9f753 100644 --- a/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractSortTest.java +++ b/fhir-persistence/src/test/java/com/ibm/fhir/persistence/test/common/AbstractSortTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -37,6 +37,7 @@ import com.ibm.fhir.model.type.Quantity; import com.ibm.fhir.model.type.Reference; import com.ibm.fhir.model.type.Uri; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.search.context.FHIRSearchContext; import com.ibm.fhir.search.exception.FHIRSearchException; @@ -294,10 +295,10 @@ public void testResourceInvalidSortParm1_lenient() throws Exception { queryParameters.put("_lastUpdated", Collections.singletonList("ge2018-03-27")); queryParameters.put("_sort", Arrays.asList(new String[] {"bogus"})); - searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); searchContext.setPageSize(100); persistenceContext = getPersistenceContextForSearch(searchContext); - List resources = persistence.search(persistenceContext, resourceType).getResource(); + List> resources = persistence.search(persistenceContext, resourceType).getResourceResults(); assertNotNull(resources); assertTrue(resources.size() > 0); } @@ -314,7 +315,7 @@ public void testResourceInvalidSortParm1_strict() throws Exception { queryParameters.put("_lastUpdated", Collections.singletonList("ge2018-03-27")); queryParameters.put("_sort", Arrays.asList(new String[] {"bogus"})); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } /** * Tests a system-level search with a sort parameter that is defined for the FHIR Resource type, @@ -355,18 +356,18 @@ public void testResourceValidSortParm1() throws Exception { searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters); searchContext.setPageSize(1000); persistenceContext = getPersistenceContextForSearch(searchContext); - List resources = persistence.search(persistenceContext, resourceType).getResource(); + List> resources = persistence.search(persistenceContext, resourceType).getResourceResults(); assertNotNull(resources); assertFalse(resources.isEmpty()); String previousId = null; String currentId = null; // Verify that resources are sorted in ascending order of logical id. - for (Resource resource : resources) { + for (ResourceResult resourceResult : resources) { + Resource resource = resourceResult.getResource(); if (previousId == null) { previousId = resource.getId(); - } - else { + } else { currentId = resource.getId(); assertTrue(previousId.compareTo(resource.getId()) <=0); previousId = currentId; @@ -392,14 +393,15 @@ public void testResourceValidSortParm2() throws Exception { searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters); searchContext.setPageSize(1000); persistenceContext = getPersistenceContextForSearch(searchContext); - List resources = persistence.search(persistenceContext, resourceType).getResource(); + List> resources = persistence.search(persistenceContext, resourceType).getResourceResults(); assertNotNull(resources); assertFalse(resources.isEmpty()); Instant previousLastUpdated = null; Instant currentLastUpdated = null; // Verify that resources are sorted in ascending order of last updated. - for (Resource resource : resources) { + for (ResourceResult resourceResult : resources) { + Resource resource = resourceResult.getResource(); if (previousLastUpdated == null) { previousLastUpdated = resource.getMeta().getLastUpdated(); } @@ -429,14 +431,15 @@ public void testResourceValidSortParm3() throws Exception { searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters); searchContext.setPageSize(1000); persistenceContext = getPersistenceContextForSearch(searchContext); - List resources = persistence.search(persistenceContext, resourceType).getResource(); + List> resources = persistence.search(persistenceContext, resourceType).getResourceResults(); assertNotNull(resources); assertFalse(resources.isEmpty()); String previousId = null; String currentId = null; // Verify that resources are sorted in descending order of logical id. - for (Resource resource : resources) { + for (ResourceResult resourceResult : resources) { + Resource resource = resourceResult.getResource(); if (previousId == null) { previousId = resource.getId(); } @@ -466,14 +469,15 @@ public void testResourceValidSortParm4() throws Exception { searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters); searchContext.setPageSize(1000); persistenceContext = getPersistenceContextForSearch(searchContext); - List resources = persistence.search(persistenceContext, resourceType).getResource(); + List> resources = persistence.search(persistenceContext, resourceType).getResourceResults(); assertNotNull(resources); assertFalse(resources.isEmpty()); Instant previousLastUpdated = null; Instant currentLastUpdated = null; // Verify that resources are sorted in descending order of last updated. - for (Resource resource : resources) { + for (ResourceResult resourceResult : resources) { + Resource resource = resourceResult.getResource(); if (previousLastUpdated == null) { previousLastUpdated = resource.getMeta().getLastUpdated(); } 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 82c0bf7d4fd..544baa4593f 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -21,6 +21,18 @@ */ public interface FHIRSearchContext extends FHIRPagingContext { + /** + * Should the search result include the actual resource in the response + * @return + */ + boolean isIncludeResourceData(); + + /** + * Set the includeResourceData flag + * @param flag + */ + void setIncludeResourceData(boolean flag); + List getSearchResourceTypes(); void setSearchResourceTypes(List searchResourceTypes); 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 f0f22461bad..35cd6636130 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,7 +18,9 @@ import com.ibm.fhir.search.parameters.QueryParameter; import com.ibm.fhir.search.parameters.SortParameter; - +/** + * Implementation of {@link FHIRSearchContext} + */ public class FHIRSearchContextImpl extends FHIRPagingContextImpl implements FHIRSearchContext { private List searchResourceTypes = null; private List searchParameters = new ArrayList<>(); @@ -29,7 +31,13 @@ public class FHIRSearchContextImpl extends FHIRPagingContextImpl implements FHIR private SummaryValueSet summaryParameter = null; private TotalValueSet totalParameter = null; private List outcomeIssues = null; + + // should the search result Bundle include the actual resource for each result entry + private boolean includeResourceData = true; + /** + * Public constructor + */ public FHIRSearchContextImpl() { searchParameters = new ArrayList<>(); } @@ -180,4 +188,14 @@ public String toString() { builder.append("]"); return builder.toString(); } -} + + @Override + public boolean isIncludeResourceData() { + return this.includeResourceData; + } + + @Override + public void setIncludeResourceData(boolean flag) { + this.includeResourceData = flag; + } +} \ No newline at end of file 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 6f2aca58090..3ad298c72a9 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 @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -369,14 +369,15 @@ public static Map> extractParameterValues(Re public static FHIRSearchContext parseQueryParameters(Class resourceType, Map> queryParameters) throws Exception { - return parseQueryParameters(resourceType, queryParameters, false); + return parseQueryParameters(resourceType, queryParameters, false, true); } public static FHIRSearchContext parseQueryParameters(Class resourceType, - Map> queryParameters, boolean lenient) throws Exception { + Map> queryParameters, boolean lenient, boolean includeResource) throws Exception { FHIRSearchContext context = FHIRSearchContextFactory.createSearchContext(); context.setLenient(lenient); + context.setIncludeResourceData(includeResource); List parameters = new ArrayList<>(); HashSet resourceTypes = new LinkedHashSet<>(); @@ -1189,13 +1190,13 @@ public static FHIRSearchContext parseReadQueryParameters(Class resourceType, log.log(Level.FINE, "Error while parsing search parameter '" + nonGeneralParam + "' for resource type " + resourceTypeName, se); } - return parseCompartmentQueryParameters(null, null, resourceType, queryParameters, lenient); + return parseCompartmentQueryParameters(null, null, resourceType, queryParameters, lenient, true); } public static FHIRSearchContext parseCompartmentQueryParameters(String compartmentName, String compartmentLogicalId, Class resourceType, Map> queryParameters) throws Exception { - return parseCompartmentQueryParameters(compartmentName, compartmentLogicalId, resourceType, queryParameters, true); + return parseCompartmentQueryParameters(compartmentName, compartmentLogicalId, resourceType, queryParameters, true, true); } /** @@ -1211,17 +1212,23 @@ public static boolean useStoredCompartmentParam() { } /** + * @param compartmentName + * @param compartmentLogicalId + * @param resourceType + * @param queryParameters * @param lenient * Whether to ignore unknown or unsupported parameter + * @param includeResource + * Whether to include the resource from the result (return handling prefer != minimal) * @return * @throws Exception */ public static FHIRSearchContext parseCompartmentQueryParameters(String compartmentName, String compartmentLogicalId, - Class resourceType, Map> queryParameters, boolean lenient) throws Exception { + Class resourceType, Map> queryParameters, boolean lenient, boolean includeResource) throws Exception { Set compartmentLogicalIds = Collections.singleton(compartmentLogicalId); QueryParameter inclusionCriteria = buildInclusionCriteria(compartmentName, compartmentLogicalIds, resourceType.getSimpleName()); - FHIRSearchContext context = parseQueryParameters(resourceType, queryParameters, lenient); + FHIRSearchContext context = parseQueryParameters(resourceType, queryParameters, lenient, includeResource); // Add the inclusion criteria to the front of the search parameter list if (inclusionCriteria != null) { diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java index cbfb4d50ec2..abbb260b0b0 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingBoxTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019,2020 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -168,7 +168,7 @@ public void testLocationBoundaryBadUnit() throws FHIRSearchException { public void testLocationBoundaryPositionsFromParameters_tenant_near() throws Exception { Map> queryParms = new HashMap>(1); queryParms.put("near", Collections.singletonList("-90.0|0.0|1.0|km")); - FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, queryParms, true); + FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, queryParms, true, true); NearLocationHandler handler = new NearLocationHandler(); List bounding = handler.generateLocationPositionsFromParameters(ctx.getSearchParameters()); assertNotNull(bounding); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingRadiusTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingRadiusTest.java index 90eb1b6dd88..41e5a53213d 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingRadiusTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/location/NearLocationHandlerBoundingRadiusTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2021 + * (C) Copyright IBM Corp. 2019, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -33,7 +33,7 @@ public class NearLocationHandlerBoundingRadiusTest extends BaseSearchTest { public void testLocationBoundaryPositionsFromParameters() throws Exception { Map> queryParms = new HashMap>(1); queryParms.put("near", Collections.singletonList("-90.0|0.0|1.0|mi_us")); - FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, queryParms, true); + FHIRSearchContext ctx = SearchUtil.parseQueryParameters(Location.class, queryParms, true, true); NearLocationHandler handler = new NearLocationHandler(); handler.setBounding(true); List bounding = handler.generateLocationPositionsFromParameters(ctx.getSearchParameters()); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/sort/test/SortParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/sort/test/SortParameterParseTest.java index 5b7b1ffd1ae..255196e65ec 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/sort/test/SortParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/sort/test/SortParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -95,7 +95,7 @@ public void testUnknownSortParm_lenient() throws Exception { // In lenient mode, invalid search parameters should be ignored queryParameters.put("_sort", Collections.singletonList("bogusSortParm")); - searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.getSortParameters() == null || searchContext.getSortParameters().isEmpty()); @@ -112,7 +112,7 @@ public void testUnknownSortParm_strict() throws Exception { // In strict mode (lenient=false), the search should throw a FHIRSearchException queryParameters.put("_sort", Collections.singletonList("bogusSortParm")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } /** diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/CompartmentParseQueryParmsTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/CompartmentParseQueryParmsTest.java index 3edaf845049..ee6ac6d3275 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/CompartmentParseQueryParmsTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/CompartmentParseQueryParmsTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -230,7 +230,7 @@ public void testCompartmentWithFakeQueryParm(boolean useStoredCompartmentParam) assertFalse(selfUri.contains(queryString), selfUri + " contain unexpectedExceptions query parameter 'fakeParameter'"); try { - SearchUtil.parseCompartmentQueryParameters(compartmentName, compartmentLogicalId, resourceType, queryParameters, false); + SearchUtil.parseCompartmentQueryParameters(compartmentName, compartmentLogicalId, resourceType, queryParameters, false, true); fail("expectedExceptions parseQueryParameters to throw due to strict mode but it didn't."); } catch (Exception e) { assertTrue(e instanceof FHIRSearchException); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/ElementsParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/ElementsParameterParseTest.java index b313ced19a6..8da1eb5be30 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/ElementsParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/ElementsParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2018, 2021 + * (C) Copyright IBM Corp. 2018, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -50,7 +50,7 @@ public void testFake_singleElement_lenient() throws Exception { String queryString = "&_elements=_id"; queryParameters.put("_elements", Collections.singletonList("bogus")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(context); assertTrue(context.getElementsParameters() == null || context.getElementsParameters().size() == 0); @@ -65,7 +65,7 @@ public void testFake_singleElement_strict() throws Exception { Class resourceType = Patient.class; queryParameters.put("_elements", Collections.singletonList("bogus")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -74,7 +74,7 @@ public void testFake_multiElements_lenient() throws Exception { Class resourceType = Patient.class; queryParameters.put("_elements", Collections.singletonList("id,contact,bogus,name")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(context); assertNotNull(context.getElementsParameters()); assertEquals(3, context.getElementsParameters().size()); @@ -93,7 +93,7 @@ public void testFake_multiElements_strict() throws Exception { Class resourceType = Patient.class; queryParameters.put("_elements", Collections.singletonList("id,contact,bogus,name")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -102,7 +102,7 @@ public void testFake_multiElementParams_lenient() throws Exception { Class resourceType = Patient.class; queryParameters.put("_elements", Arrays.asList("id","contact","bogus","name")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(context); assertNotNull(context.getElementsParameters()); assertEquals(1, context.getElementsParameters().size()); @@ -118,7 +118,7 @@ public void testFake_multiElementParams_strict() throws Exception { Class resourceType = Patient.class; queryParameters.put("_elements", Arrays.asList("id","contact","bogus","name")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/InclusionParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/InclusionParameterParseTest.java index 11712b668d3..d6459ca7836 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/InclusionParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/InclusionParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2018, 2021 + * (C) Copyright IBM Corp. 2018, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -66,7 +66,7 @@ public void testIncludeInvalidSyntax_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include", Collections.singletonList("xxx")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -150,7 +150,7 @@ public void testIncludeInvalidJoinResourceTypeLenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include", Collections.singletonList("MedicationOrder:patient")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -171,7 +171,7 @@ public void testIncludeInvalidJoinResourceTypeNonLenient() throws Exception { // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_include", Collections.singletonList("MedicationOrder:patient")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -184,7 +184,7 @@ public void testIncludeUnknownParameterName_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include", Collections.singletonList("Patient:bogus")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -205,7 +205,7 @@ public void testIncludeUnknownParameterName_strict() throws Exception { // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_include", Collections.singletonList("Patient:bogus")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -218,7 +218,7 @@ public void testIncludeInvalidParameterType_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include", Collections.singletonList("Patient:active")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -324,7 +324,7 @@ public void testIncludeInvalidTargetType_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include", Collections.singletonList("Patient:careprovider:Contract")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -345,7 +345,7 @@ public void testIncludeInvalidTargetType() throws Exception { // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_include", Collections.singletonList("Patient:careprovider:Contract")); - System.out.println(SearchUtil.parseQueryParameters(resourceType, queryParameters, false)); + System.out.println(SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true)); } @Test @@ -394,7 +394,7 @@ public void testIncludeRevIncludeSummaryText_lenient() throws Exception { queryParameters.put("_include", Collections.singletonList("Patient:general-practitioner:Practitioner")); queryParameters.put("_revinclude", Collections.singletonList("Patient:link:Patient")); queryParameters.put("_summary", Collections.singletonList("text")); - searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertFalse(searchContext.hasIncludeParameters()); @@ -465,7 +465,7 @@ public void testRevIncludeInvalidJoinResourceType_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_revinclude", Collections.singletonList("Invalid:general-practitioner")); queryParameters.put("_revinclude", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasRevIncludeParameters()); assertEquals(1, searchContext.getRevIncludeParameters().size()); @@ -486,7 +486,7 @@ public void testRevIncludeInvalidJoinResourceType() throws Exception { // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_revinclude", Collections.singletonList("Invalid:general-practitioner")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -499,7 +499,7 @@ public void testRevIncludeInvalidTargetType_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_revinclude", Collections.singletonList("Patient:general-practitioner:Practitioner")); queryParameters.put("_revinclude", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasRevIncludeParameters()); assertEquals(1, searchContext.getRevIncludeParameters().size()); @@ -533,7 +533,7 @@ public void testRevIncludeUnknownParameterName_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_revinclude", Collections.singletonList("Patient:bogus")); queryParameters.put("_revinclude", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasRevIncludeParameters()); assertEquals(1, searchContext.getRevIncludeParameters().size()); @@ -554,7 +554,7 @@ public void testRevIncludeUnknownParameterName_strict() throws Exception { // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_revinclude", Collections.singletonList("Patient:bogus")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -567,7 +567,7 @@ public void testRevIncludeInvalidParameterType_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_revinclude", Collections.singletonList("Patient:active")); queryParameters.put("_revinclude", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasRevIncludeParameters()); assertEquals(1, searchContext.getRevIncludeParameters().size()); @@ -588,7 +588,7 @@ public void testRevIncludeInvalidParameterType() throws Exception { // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_revinclude", Collections.singletonList("Patient:active")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -1023,7 +1023,7 @@ public void testIterateIncludeInvalidModifier_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include:invalid", Collections.singletonList("Patient:link")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -1057,7 +1057,7 @@ public void testIterateIncludeInvalidJoinResourceType_lenient() throws Exception // In lenient mode, the invalid parameter should be ignored queryParameters.put("_include:iterate", Collections.singletonList("MedicationOrder:patient")); queryParameters.put("_include", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasIncludeParameters()); assertEquals(1, searchContext.getIncludeParameters().size()); @@ -1078,7 +1078,7 @@ public void testIterateIncludeInvalidJoinResourceType_strict() throws Exception // In strict mode, the query should throw a FHIRSearchException queryParameters.put("_include:iterate", Collections.singletonList("MedicationOrder:patient")); - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } @Test @@ -1091,7 +1091,7 @@ public void testIterateRevIncludeInvalidTargetType_lenient() throws Exception { // In lenient mode, the invalid parameter should be ignored queryParameters.put("_revinclude:iterate", Collections.singletonList("Patient:general-practitioner:Practitioner")); queryParameters.put("_revinclude", Collections.singletonList("Patient:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(searchContext); assertTrue(searchContext.hasRevIncludeParameters()); assertEquals(1, searchContext.getRevIncludeParameters().size()); @@ -1132,7 +1132,7 @@ public void testIterateRevIncludeInvalidTargetTypeNotUserSpecified_strict() thro // In strict mode, the invalid parameter should be ignored if not explicitly specified by the user queryParameters.put("_revinclude:iterate", Collections.singletonList("Patient:general-practitioner")); queryParameters.put("_revinclude", Collections.singletonList("PractitionerRole:organization")); - FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + FHIRSearchContext searchContext = SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); assertNotNull(searchContext); assertTrue(searchContext.hasRevIncludeParameters()); assertEquals(expectedIncludeParms.size(), searchContext.getRevIncludeParameters().size()); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/PageCountParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/PageCountParameterParseTest.java index 3e8fc3aa7dd..26b865db37c 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/PageCountParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/PageCountParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -61,7 +61,7 @@ public void testPageAndCountInvalid_lenient() throws Exception { queryParameters.put("_page", Arrays.asList("invalid")); queryParameters.put("_count", Arrays.asList("-1")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true, true); assertNotNull(context); assertEquals(context.getPageNumber(), 1); assertEquals(context.getPageSize(), 10); @@ -75,7 +75,7 @@ public void testPageAndCountInvalidPage_strict() throws Exception { queryParameters.put("_page", Arrays.asList("invalid")); queryParameters.put("_count", Arrays.asList("20")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "An error occurred while parsing parameter '_page'."); @@ -92,7 +92,7 @@ public void testPageAndCountInvalidCount_strict() throws Exception { queryParameters.put("_page", Arrays.asList("2")); queryParameters.put("_count", Arrays.asList("-1")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "An error occurred while parsing parameter '_count'."); @@ -107,7 +107,7 @@ public void testPageAndCountMultipleParams_lenient() throws Exception { queryParameters.put("_page", Arrays.asList("3", "4")); queryParameters.put("_count", Arrays.asList("30", "40")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true, true); assertNotNull(context); assertEquals(context.getPageNumber(), 3); assertEquals(context.getPageSize(), 30); @@ -121,7 +121,7 @@ public void testPageAndCountMultiplePageParams_strict() throws Exception { queryParameters.put("_page", Arrays.asList("3", "4")); queryParameters.put("_count", Arrays.asList("30")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "Search parameter '_page' is specified multiple times"); @@ -138,7 +138,7 @@ public void testPageAndCountMultipleCountParams_strict() throws Exception { queryParameters.put("_page", Arrays.asList("3")); queryParameters.put("_count", Arrays.asList("30", "40")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "Search parameter '_count' is specified multiple times"); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/SummaryParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/SummaryParameterParseTest.java index 330f3731bd8..1de8d318428 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/SummaryParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/SummaryParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2018, 2021 + * (C) Copyright IBM Corp. 2018, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -50,7 +50,7 @@ public void testSummaryMultiple_lenient() throws Exception { Class resourceType = Patient.class; queryParameters.put("_summary", Arrays.asList("data","true")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(context); assertNotNull(context.getSummaryParameter()); assertEquals(context.getSummaryParameter(), SummaryValueSet.DATA); @@ -64,7 +64,7 @@ public void testSummaryMultiple_strict() throws Exception { queryParameters.put("_summary", Arrays.asList("data","true")); try { - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "Search parameter '_summary' is specified multiple times"); @@ -79,7 +79,7 @@ public void testSummaryInvalid_lenient() throws Exception { Class resourceType = Patient.class; queryParameters.put("_summary", Arrays.asList("invalid")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(resourceType, queryParameters, true, true); assertNotNull(context); assertNull(context.getSummaryParameter()); } @@ -92,7 +92,7 @@ public void testSummaryInvalid_strict() throws Exception { queryParameters.put("_summary", Arrays.asList("invalid")); try { - SearchUtil.parseQueryParameters(resourceType, queryParameters, false); + SearchUtil.parseQueryParameters(resourceType, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "An error occurred while parsing parameter '_summary'."); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/TotalParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/TotalParameterParseTest.java index a6429463289..438f76917d1 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/TotalParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/TotalParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -56,7 +56,7 @@ public void testTotalInvalid_lenient() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_total", Arrays.asList("invalid")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true, true); assertNotNull(context); assertNull(context.getTotalParameter()); } @@ -68,7 +68,7 @@ public void testTotalInvalid_strict() throws Exception { queryParameters.put("_total", Arrays.asList("invalid")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "An error occurred while parsing parameter '_total'."); @@ -82,7 +82,7 @@ public void testTotalMultipleParams_lenient() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_total", Arrays.asList("none", "accurate")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true, true); assertNotNull(context); assertEquals(context.getTotalParameter(), TotalValueSet.NONE); } @@ -94,7 +94,7 @@ public void testTotalMultipleParams_strict() throws Exception { queryParameters.put("_total", Arrays.asList("none", "accurate")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "Search parameter '_total' is specified multiple times"); diff --git a/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java b/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java index da33b21a584..39edb64ffe6 100644 --- a/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java +++ b/fhir-search/src/test/java/com/ibm/fhir/search/test/TypeParameterParseTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -63,7 +63,7 @@ public void testTypeNonSystemSearch_lenient() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_type", Arrays.asList("Patient")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Patient.class, queryParameters, true, true); assertNotNull(context); assertNull(context.getSearchResourceTypes()); } @@ -75,7 +75,7 @@ public void testTypeNonSystemSearch_strict() throws Exception { queryParameters.put("_type", Arrays.asList("Patient")); try { - SearchUtil.parseQueryParameters(Patient.class, queryParameters, false); + SearchUtil.parseQueryParameters(Patient.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "_type search parameter is only supported with system search"); @@ -89,7 +89,7 @@ public void testTypeMultipleParams_lenient() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_type", Arrays.asList("Patient", "Practitioner")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Resource.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Resource.class, queryParameters, true, true); assertNotNull(context); assertNotNull(context.getSearchResourceTypes()); assertEquals(context.getSearchResourceTypes().size(), 1); @@ -103,7 +103,7 @@ public void testTypeMultipleParams_strict() throws Exception { queryParameters.put("_type", Arrays.asList("Patient", "Practitioner")); try { - SearchUtil.parseQueryParameters(Resource.class, queryParameters, false); + SearchUtil.parseQueryParameters(Resource.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "Search parameter '_type' is specified multiple times"); @@ -117,7 +117,7 @@ public void testTypeInvalid_lenient() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_type", Collections.singletonList("invalid,Practitioner")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Resource.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Resource.class, queryParameters, true, true); assertNotNull(context); assertNotNull(context.getSearchResourceTypes()); assertEquals(context.getSearchResourceTypes().size(), 1); @@ -131,7 +131,7 @@ public void testTypeInvalid_strict() throws Exception { queryParameters.put("_type", Collections.singletonList("invalid,Practitioner")); try { - SearchUtil.parseQueryParameters(Resource.class, queryParameters, false); + SearchUtil.parseQueryParameters(Resource.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "_type search parameter has invalid resource type: invalid"); @@ -145,7 +145,7 @@ public void testTypeAbstract_lenient() throws Exception { Map> queryParameters = new HashMap<>(); queryParameters.put("_type", Collections.singletonList("Resource")); - FHIRSearchContext context = SearchUtil.parseQueryParameters(Resource.class, queryParameters, true); + FHIRSearchContext context = SearchUtil.parseQueryParameters(Resource.class, queryParameters, true, true); assertNotNull(context); assertNull(context.getSearchResourceTypes()); } @@ -157,7 +157,7 @@ public void testTypeAbstract_strict() throws Exception { queryParameters.put("_type", Collections.singletonList("Resource")); try { - SearchUtil.parseQueryParameters(Resource.class, queryParameters, false); + SearchUtil.parseQueryParameters(Resource.class, queryParameters, false, true); } catch(Exception ex) { isExceptionThrown = true; assertEquals(ex.getMessage(), "_type search parameter has invalid resource type: Resource"); diff --git a/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIRResourceHelpers.java b/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIRResourceHelpers.java index b21673350fd..53934657bb1 100644 --- a/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIRResourceHelpers.java +++ b/fhir-server-spi/src/main/java/com/ibm/fhir/server/spi/operation/FHIRResourceHelpers.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -445,11 +445,13 @@ Bundle doSearch(String type, String compartment, String compartmentId, Multivalu * the resource context * @param checkIfInteractionAllowed * if true, check that the search interaction is permitted + * @param alwaysIncludeResources + * if true, ignore any return preference and always include the resource in the search result bundle * @return a Bundle containing the search result set * @throws Exception */ Bundle doSearch(String type, String compartment, String compartmentId, MultivaluedMap queryParameters, - String requestUri, Resource contextResource, boolean checkIfInteractionAllowed) throws Exception; + String requestUri, Resource contextResource, boolean checkIfInteractionAllowed, boolean alwaysIncludeResources) throws Exception; /** * Helper method which invokes a custom operation. diff --git a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java index f82d98fe521..f9d4f4fe094 100644 --- a/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java +++ b/fhir-server-test/src/test/java/com/ibm/fhir/server/test/SearchTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -2398,4 +2398,36 @@ public void testSearchWithTenant2_OverrideUri() { String selfLink = getSelfLink(bundle); assertEquals(selfLink, "https://chocolate.fudge/Patient?_count=11&_page=1"); } + + @Test(groups = { "server-search" }, dependsOnMethods = {"testCreatePatient" }) + public void testSearchPatientWithIdentifierNoData() { + WebTarget target = getWebTarget(); + Response response = + target.path("Patient").queryParam("identifier", "test|"+ patientIdentifierValue) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .header("X-FHIR-TENANT-ID", tenantName) + .header("X-FHIR-DSID", dataStoreId) + .header("Prefer", "return=minimal") + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + Bundle bundle = response.readEntity(Bundle.class); + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + assertNull(bundle.getEntry().get(0).getResource()); + assertNotNull(bundle.getEntry().get(0).getResponse()); + + response = + target.path("Patient").queryParam("identifier", "test|"+ patientIdentifierValue.toUpperCase()) + .request(FHIRMediaType.APPLICATION_FHIR_JSON) + .header("X-FHIR-TENANT-ID", tenantName) + .header("X-FHIR-DSID", dataStoreId) + .header("Prefer", "return=minimal") + .get(); + assertResponse(response, Response.Status.OK.getStatusCode()); + bundle = response.readEntity(Bundle.class); + assertNotNull(bundle); + assertTrue(bundle.getEntry().size() >= 1); + assertNull(bundle.getEntry().get(0).getResource()); + assertNotNull(bundle.getEntry().get(0).getResponse()); + } } \ No newline at end of file diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/filter/rest/FHIRRestServletFilter.java b/fhir-server/src/main/java/com/ibm/fhir/server/filter/rest/FHIRRestServletFilter.java index 9c8d6e483c8..205b0aca4b3 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/filter/rest/FHIRRestServletFilter.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/filter/rest/FHIRRestServletFilter.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -275,6 +275,10 @@ private void computeReturnPref(FHIRRequestContext context, ServletRequest reques try { returnPref = HTTPReturnPreference.from(returnPrefString); isDefault = false; + + if (log.isLoggable(Level.FINE)) { + log.fine("Requested return preference = " + returnPref); + } } catch (IllegalArgumentException e) { String message = "Invalid HTTP return preference passed in header 'Prefer': '" + returnPrefString + "'"; if (handlingPref == HTTPHandlingPreference.STRICT) { diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/registry/ServerRegistryResourceProvider.java b/fhir-server/src/main/java/com/ibm/fhir/server/registry/ServerRegistryResourceProvider.java index 264cc681d4f..3bdc7302c74 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/registry/ServerRegistryResourceProvider.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/registry/ServerRegistryResourceProvider.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2020, 2021 + * (C) Copyright IBM Corp. 2020, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -123,13 +123,14 @@ private List computeRegistryResources(Class result = persistence.search(context, resourceType); + MultiResourceResult result = persistence.search(context, resourceType); if (result.isSuccess()) { transactionHelper.commit(); transactionHelper = null; - return result.getResource().stream() + return result.getResourceResults().stream() + .map(rr -> rr.getResource()) .map(FHIRRegistryResource::from) .filter(Objects::nonNull) .sorted() @@ -161,11 +162,12 @@ private Collection getRegistryResources(Class result = persistence.search(context, resourceType); + MultiResourceResult result = persistence.search(context, resourceType); if (result.isSuccess()) { - List registryResources = new ArrayList<>(searchContext.getTotalCount() != null ? searchContext.getTotalCount() : result.getResource().size()); - registryResources.addAll(result.getResource().stream() + List registryResources = new ArrayList<>(searchContext.getTotalCount() != null ? searchContext.getTotalCount() : result.getResourceResults().size()); + registryResources.addAll(result.getResourceResults().stream() + .map(rr -> rr.getResource()) .map(FHIRRegistryResource::from) .filter(Objects::nonNull) .collect(Collectors.toList())); @@ -175,7 +177,8 @@ private Collection getRegistryResources(Class rr.getResource()) .map(FHIRRegistryResource::from) .filter(Objects::nonNull) .collect(Collectors.toList())); diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorMeta.java b/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorMeta.java index 89221fa6383..714b9a3d349 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorMeta.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorMeta.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,7 +9,6 @@ import static com.ibm.fhir.model.type.String.string; import java.time.ZoneOffset; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -17,7 +16,6 @@ import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response.Status; @@ -44,7 +42,6 @@ import com.ibm.fhir.persistence.payload.PayloadPersistenceResponse; import com.ibm.fhir.search.SearchConstants; import com.ibm.fhir.search.exception.FHIRSearchException; -import com.ibm.fhir.server.exception.FHIRRestBundledRequestException; import com.ibm.fhir.server.spi.operation.FHIROperationContext; import com.ibm.fhir.server.spi.operation.FHIRResourceHelpers; import com.ibm.fhir.server.spi.operation.FHIRResourceHelpers.Interaction; @@ -332,7 +329,7 @@ private void resolveConditionalReferences(Resource resource) throws Exception { queryParameters.add("_count", "1"); // Do a search, but no need to check if the interaction is allowed - Bundle bundle = helpers.doSearch(type, null, null, queryParameters, null, resource, false); + Bundle bundle = helpers.doSearch(type, null, null, queryParameters, null, resource, false, true); int total = bundle.getTotal().getValue(); diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorPersist.java b/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorPersist.java index ebc044063a6..8838dafed24 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorPersist.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/rest/FHIRRestInteractionVisitorPersist.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,6 @@ import java.util.concurrent.Callable; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import com.ibm.fhir.exception.FHIROperationException; @@ -31,7 +30,6 @@ import com.ibm.fhir.persistence.exception.FHIRPersistenceResourceNotFoundException; import com.ibm.fhir.persistence.payload.PayloadPersistenceResponse; import com.ibm.fhir.search.exception.FHIRSearchException; -import com.ibm.fhir.server.exception.FHIRRestBundledRequestException; import com.ibm.fhir.server.spi.operation.FHIROperationContext; import com.ibm.fhir.server.spi.operation.FHIRResourceHelpers; import com.ibm.fhir.server.spi.operation.FHIRRestOperationResponse; @@ -73,7 +71,7 @@ public FHIRRestOperationResponse doSearch(int entryIndex, String requestDescript doInteraction(entryIndex, requestDescription, accumulatedTime, () -> { Bundle searchResults = helpers.doSearch(type, compartment, compartmentId, queryParameters, requestUri, - contextResource, checkInteractionAllowed); + contextResource, checkInteractionAllowed, true); return Bundle.Entry.builder() .resource(searchResults) diff --git a/fhir-server/src/main/java/com/ibm/fhir/server/util/FHIRRestHelper.java b/fhir-server/src/main/java/com/ibm/fhir/server/util/FHIRRestHelper.java index 4ca3604dd58..bbf55c9bdb2 100644 --- a/fhir-server/src/main/java/com/ibm/fhir/server/util/FHIRRestHelper.java +++ b/fhir-server/src/main/java/com/ibm/fhir/server/util/FHIRRestHelper.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2016, 2021 + * (C) Copyright IBM Corp. 2016, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -92,8 +92,10 @@ import com.ibm.fhir.persistence.FHIRPersistenceTransaction; import com.ibm.fhir.persistence.HistorySortOrder; import com.ibm.fhir.persistence.InteractionStatus; +import com.ibm.fhir.persistence.MultiResourceResult; import com.ibm.fhir.persistence.ResourceChangeLogRecord; import com.ibm.fhir.persistence.ResourceEraseRecord; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.context.FHIRHistoryContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; @@ -260,7 +262,7 @@ public FHIRRestOperationResponse doCreateMeta(FHIRPersistenceEvent event, List searchParameters = getQueryParameterMap(ifNoneExist); - responseBundle = doSearch(type, null, null, searchParameters, null, resource, false); + responseBundle = doSearch(type, null, null, searchParameters, null, resource, false, true); } catch (FHIROperationException e) { throw e; } catch (Throwable t) { @@ -521,7 +523,7 @@ public FHIRRestOperationResponse doUpdateMeta(FHIRPersistenceEvent event, String Bundle responseBundle = null; try { MultivaluedMap searchParameters = getQueryParameterMap(searchQueryString); - responseBundle = doSearch(type, null, null, searchParameters, null, newResource, false); + responseBundle = doSearch(type, null, null, searchParameters, null, newResource, false, true); } catch (FHIROperationException e) { throw e; } catch (Throwable t) { @@ -920,7 +922,7 @@ public FHIRRestOperationResponse doDelete(String type, String id, String searchQ try { MultivaluedMap searchParameters = getQueryParameterMap(searchQueryString); searchParameters.putSingle(SearchConstants.COUNT, Integer.toString(searchPageSize)); - responseBundle = doSearch(type, null, null, searchParameters, null, null, false); + responseBundle = doSearch(type, null, null, searchParameters, null, null, false, true); } catch (FHIROperationException e) { throw e; } catch (Throwable t) { @@ -1268,9 +1270,9 @@ public Bundle doHistory(String type, String id, MultivaluedMap q FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(event, historyContext); - List resources = - persistence.history(persistenceContext, resourceType, id).getResource(); - bundle = createHistoryBundle(resources, historyContext, type); + MultiResourceResult historyResult = + persistence.history(persistenceContext, resourceType, id); + bundle = createHistoryBundle(historyResult.getResourceResults(), historyContext, type); bundle = addLinks(historyContext, bundle, requestUri); event.setFhirResource(bundle); @@ -1299,13 +1301,13 @@ public Bundle doHistory(String type, String id, MultivaluedMap q @Override public Bundle doSearch(String type, String compartment, String compartmentId, MultivaluedMap queryParameters, String requestUri, Resource contextResource) throws Exception { - return doSearch(type, compartment, compartmentId, queryParameters, requestUri, contextResource, true); + return doSearch(type, compartment, compartmentId, queryParameters, requestUri, contextResource, true, false); } @Override public Bundle doSearch(String type, String compartment, String compartmentId, MultivaluedMap queryParameters, String requestUri, - Resource contextResource, boolean checkInteractionAllowed) throws Exception { + Resource contextResource, boolean checkInteractionAllowed, boolean alwaysIncludeResources) throws Exception { log.entering(this.getClass().getName(), "doSearch"); // Validate that interaction is allowed for given resource type @@ -1333,8 +1335,13 @@ public Bundle doSearch(String type, String compartment, String compartmentId, Class resourceType = getResourceType(resourceTypeName); + final boolean isLenientHandling = HTTPHandlingPreference.LENIENT == requestContext.getHandlingPreference(); + final boolean includeResources = alwaysIncludeResources || HTTPReturnPreference.MINIMAL != requestContext.getReturnPreference() || requestContext.isReturnPreferenceDefault(); + if (!includeResources) { + log.info("Not including resources"); + } FHIRSearchContext searchContext = SearchUtil.parseCompartmentQueryParameters(compartment, compartmentId, resourceType, queryParameters, - HTTPHandlingPreference.LENIENT.equals(requestContext.getHandlingPreference())); + isLenientHandling, includeResources); // First, invoke the 'beforeSearch' interceptor methods. FHIRPersistenceEvent event = @@ -1343,10 +1350,10 @@ public Bundle doSearch(String type, String compartment, String compartmentId, FHIRPersistenceContext persistenceContext = FHIRPersistenceContextFactory.createPersistenceContext(event, searchContext); - List resources = - persistence.search(persistenceContext, resourceType).getResource(); + MultiResourceResult searchResult = + persistence.search(persistenceContext, resourceType); - bundle = createSearchResponseBundle(resources, searchContext, type); + bundle = createSearchResponseBundle(searchResult.getResourceResults(), searchContext, type); if (requestUri != null) { bundle = addLinks(searchContext, bundle, requestUri); } @@ -2056,8 +2063,8 @@ private String retrieveLocalIdentifier(String fullUrl) { /** * Creates a bundle that will hold results for a search operation. * - * @param resources - * the list of resources to include in the bundle + * @param resourceResults + * the list of resource results to include in the bundle * @param searchContext * the FHIRSearchContext object associated with the search * @param type @@ -2065,7 +2072,7 @@ private String retrieveLocalIdentifier(String fullUrl) { * @return the bundle * @throws Exception */ - Bundle createSearchResponseBundle(List resources, FHIRSearchContext searchContext, String type) throws Exception { + Bundle createSearchResponseBundle(List> resourceResults, FHIRSearchContext searchContext, String type) throws Exception { // throws if we have a count of more than 2,147,483,647 resources UnsignedInt totalCount = searchContext.getTotalCount() != null ? UnsignedInt.of(searchContext.getTotalCount()) : null; @@ -2075,13 +2082,13 @@ Bundle createSearchResponseBundle(List resources, FHIRSearchContext se .id(UUID.randomUUID().toString()) .total(totalCount); - if (resources.size() > 0) { + if (resourceResults.size() > 0) { // Calculate how many resources are 'match' mode int matchResourceCount = searchContext.getMatchCount(); - List matchResources = resources.subList(0, matchResourceCount); + List> matchResources = resourceResults.subList(0, matchResourceCount); // Check if too many included resources - if (resources.size() > matchResourceCount + searchContext.getMaxPageIncludeCount()) { + if (resourceResults.size() > matchResourceCount + searchContext.getMaxPageIncludeCount()) { throw buildRestException("Number of returned 'include' resources exceeds allowable limit of " + searchContext.getMaxPageIncludeCount(), IssueType.BUSINESS_RULE, IssueSeverity.ERROR); } @@ -2118,7 +2125,8 @@ Bundle createSearchResponseBundle(List resources, FHIRSearchContext se issues = performSearchReferenceChecks(type, chainedSearchParameters, logicalIdReferenceSearchParameters, matchResources); } - for (Resource resource : resources) { + for (ResourceResult resourceResult : resourceResults) { + Resource resource = resourceResult.getResource(); Entry.Builder entryBuilder = Entry.builder(); if (resource != null) { if (resource.getId() != null) { @@ -2130,10 +2138,20 @@ Bundle createSearchResponseBundle(List resources, FHIRSearchContext se issues.add(FHIRUtil.buildOperationOutcomeIssue(IssueSeverity.WARNING, IssueType.NOT_SUPPORTED, msg)); } entryBuilder.resource(resource); - } else { + } else if (searchContext.isIncludeResourceData()) { String msg = "A resource with no data was found."; log.warning(msg); issues.add(FHIRUtil.buildOperationOutcomeIssue(IssueSeverity.WARNING, IssueType.NOT_SUPPORTED, msg)); + } else { + // Off-spec - simply provide the url without the resource body. But in order + // to satisfy "Rule: must be a resource unless there's a request or response" + // we also add a minimal response element + final Uri uri = Uri.of(getRequestBaseUri(type) + "/" + resourceResult.getResourceTypeName() + "/" + resourceResult.getLogicalId()); + com.ibm.fhir.model.resource.Bundle.Entry.Response response = + com.ibm.fhir.model.resource.Bundle.Entry.Response.builder() + .status("200") + .build(); + entryBuilder.fullUrl(uri).response(response); } // Search mode is determined by the matchResourceCount, which will be decremented each time through the loop. // If the count is greater than 0, the mode is MATCH. If less than or equal to 0, the mode is INCLUDE. @@ -2188,7 +2206,7 @@ Bundle createSearchResponseBundle(List resources, FHIRSearchContext se * @throws Exception if multiple resource types containing the same logical ID are found */ private List performSearchReferenceChecks(String resourceType, List chainQueryParameters, - List logicalIdReferenceQueryParameters, List matchResources) throws Exception { + List logicalIdReferenceQueryParameters, List> matchResources) throws Exception { List issues = new ArrayList<>(); if (!chainQueryParameters.isEmpty() || !logicalIdReferenceQueryParameters.isEmpty()) { @@ -2213,9 +2231,14 @@ private List performSearchReferenceChecks(String resourceType, List resourceResult : matchResources) { + Resource resource = resourceResult.getResource(); + if (resource == null) { + log.warning("Unexpected null resource: " + resourceResult.toString()); + throw new FHIRPersistenceException("Search reference check contained a null resource"); + } + // A flag that indicates whether we need to take a closer look at the reference values or not boolean needsEval = false; @@ -2298,7 +2321,7 @@ private void validateReferenceParams(List chainQueryParameters, * @return the bundle * @throws Exception */ - private Bundle createHistoryBundle(List resources, FHIRHistoryContext historyContext, String type) + private Bundle createHistoryBundle(List> resourceResults, FHIRHistoryContext historyContext, String type) throws Exception { // throws if we have a count of more than 2,147,483,647 resources @@ -2309,32 +2332,22 @@ private Bundle createHistoryBundle(List resources, FHIRHisto .id(UUID.randomUUID().toString()) .total(totalCount); - Map> deletedResourcesMap = historyContext.getDeletedResources(); + for (int i = 0; i < resourceResults.size(); i++) { + ResourceResult resourceResult = resourceResults.get(i); + Resource resource = resourceResult.getResource(); - for (int i = 0; i < resources.size(); i++) { - Resource resource = resources.get(i); - - if (resource == null) { - String msg = "A resource with no data was found."; - log.warning(msg); - throw new IllegalStateException(msg); - } - if (resource.getId() == null) { + // Note that the resource object may be null if it has been erased + if (resource != null && resource.getId() == null) { String msg = "A resource with no id was found."; log.warning(msg); throw new IllegalStateException(msg); } - Integer versionId = Integer.valueOf(resource.getMeta().getVersionId().getValue()); - String logicalId = resource.getId(); - String resourceType = ModelSupport.getTypeName(resource.getClass()); - List deletedVersions = deletedResourcesMap.get(logicalId); - // Determine the correct method to include in this history entry (POST, PUT, DELETE). HTTPVerb method; - if (deletedVersions != null && deletedVersions.contains(versionId)) { + if (resourceResult.isDeleted() || resource == null) { method = HTTPVerb.DELETE; - } else if (versionId == 1) { + } else if (resourceResult.getVersion() == 1) { method = HTTPVerb.POST; } else { method = HTTPVerb.PUT; @@ -2343,17 +2356,22 @@ private Bundle createHistoryBundle(List resources, FHIRHisto // Create the 'request' entry, and set the request.url field. // 'create' --> url = "" // 'update'/'delete' --> url = "/" + final String resourceType = resourceResult.getResourceTypeName(); + final String resourcePath = resourceType + "/" + resourceResult.getLogicalId(); Entry.Request request = - Entry.Request.builder().method(method).url(Url.of(method == HTTPVerb.POST - ? resourceType : resourceType + "/" + logicalId)).build(); + Entry.Request.builder().method(method) + .url(Url.of(method == HTTPVerb.POST ? resourceType : resourcePath)) + .build(); Entry.Response response = Entry.Response.builder().status(string("200")).build(); Entry entry = - Entry.builder().request(request).fullUrl(Uri.of(getRequestBaseUri(type) + "/" - + resource.getClass().getSimpleName() + "/" - + resource.getId())).response(response).resource(resource).build(); + Entry.builder().request(request) + .fullUrl(Uri.of(getRequestBaseUri(type) + "/" + resourcePath)) + .response(response) + .resource(resource) + .build(); bundleBuilder.entry(entry); } diff --git a/fhir-server/src/test/java/com/ibm/fhir/server/test/MockPersistenceImpl.java b/fhir-server/src/test/java/com/ibm/fhir/server/test/MockPersistenceImpl.java index 37efd53c3e3..ed316fb0f74 100644 --- a/fhir-server/src/test/java/com/ibm/fhir/server/test/MockPersistenceImpl.java +++ b/fhir-server/src/test/java/com/ibm/fhir/server/test/MockPersistenceImpl.java @@ -1,11 +1,11 @@ /* - * (C) Copyright IBM Corp. 2020, 2021 + * (C) Copyright IBM Corp. 2020, 2022 * * SPDX-License-Identifier: Apache-2.0 */ package com.ibm.fhir.server.test; -import java.util.ArrayList; +import java.time.ZoneOffset; import java.util.List; import java.util.function.Function; @@ -23,6 +23,7 @@ import com.ibm.fhir.persistence.MultiResourceResult; import com.ibm.fhir.persistence.ResourceChangeLogRecord; import com.ibm.fhir.persistence.ResourcePayload; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.exception.FHIRPersistenceException; @@ -113,18 +114,28 @@ public SingleResourceResult updateWithMeta(FHIRPersisten @SuppressWarnings("unchecked") @Override - public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { - T updatedResource = (T) Patient.builder().id("test").meta(Meta.builder().versionId(Id.of("1")).lastUpdated(Instant.now()).build()).build(); - return new MultiResourceResult.Builder() + public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { + + + Instant lastUpdated = Instant.now(ZoneOffset.UTC); + Patient updatedResource = Patient.builder().id("test").meta(Meta.builder().versionId(Id.of("1")).lastUpdated(lastUpdated).build()).build(); + ResourceResult resourceResult = ResourceResult.builder() + .resource(updatedResource) + .logicalId(logicalId) + .version(1) + .resourceTypeName(updatedResource.getClass().getSimpleName()) + .lastUpdated(lastUpdated.getValue().toInstant()) + .build(); + return MultiResourceResult.builder() .success(true) - .resource(updatedResource).build(); + .resourceResult(resourceResult).build(); } @Override - public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { - return new MultiResourceResult.Builder<>() + public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { + return MultiResourceResult.builder() .success(true) - .resource(new ArrayList<>()).build(); + .build(); } @Override diff --git a/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java b/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java index 0b706c7c473..462395bf23b 100644 --- a/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java +++ b/fhir-server/src/test/java/com/ibm/fhir/server/test/ServerResolveFunctionTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -51,6 +51,7 @@ import com.ibm.fhir.persistence.MultiResourceResult; import com.ibm.fhir.persistence.ResourceChangeLogRecord; import com.ibm.fhir.persistence.ResourcePayload; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceContext; import com.ibm.fhir.persistence.context.FHIRPersistenceContextFactory; @@ -357,21 +358,28 @@ public SingleResourceResult updateWithMeta( @SuppressWarnings("unchecked") @Override - public MultiResourceResult history( + public MultiResourceResult history( FHIRPersistenceContext context, - Class resourceType, + Class resourceType, String logicalId) throws FHIRPersistenceException { - List versions = (List) map.getOrDefault(resourceType, Collections.emptyMap()).getOrDefault(logicalId, Collections.emptyList()); + + List versions = map.getOrDefault(resourceType, Collections.emptyMap()).getOrDefault(logicalId, Collections.emptyList()); + + // Convert the resource list to a results list + List> resourceResults = new ArrayList<>(versions.size()); + for (Resource resource: versions) { + resourceResults.add(ResourceResult.from(resource)); + } - MultiResourceResult.Builder resultBuilder = new MultiResourceResult.Builder() + MultiResourceResult.Builder resultBuilder = MultiResourceResult.builder() .success(!versions.isEmpty()) - .resource(versions); + .addResourceResults(resourceResults); return resultBuilder.build(); } @Override - public MultiResourceResult search( + public MultiResourceResult search( FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { throw new UnsupportedOperationException(); diff --git a/fhir-server/src/test/java/com/ibm/fhir/server/util/FHIRRestHelperTest.java b/fhir-server/src/test/java/com/ibm/fhir/server/util/FHIRRestHelperTest.java index 439afeef24d..4c4ec9b09a9 100644 --- a/fhir-server/src/test/java/com/ibm/fhir/server/util/FHIRRestHelperTest.java +++ b/fhir-server/src/test/java/com/ibm/fhir/server/util/FHIRRestHelperTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2020, 2021 + * (C) Copyright IBM Corp. 2020, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,7 +14,8 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.fail; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import javax.ws.rs.core.Response; @@ -54,6 +55,7 @@ import com.ibm.fhir.model.type.code.NarrativeStatus; import com.ibm.fhir.model.type.code.ProcedureStatus; import com.ibm.fhir.persistence.FHIRPersistence; +import com.ibm.fhir.persistence.ResourceResult; import com.ibm.fhir.persistence.SingleResourceResult; import com.ibm.fhir.persistence.context.FHIRPersistenceEvent; import com.ibm.fhir.search.context.FHIRSearchContext; @@ -1982,8 +1984,10 @@ public void testBundleSearchBundleWithNullRsrcAndNoId() throws Exception { .build()) .build(); - // Process bundle - Bundle responseBundle = helper.createSearchResponseBundle(Arrays.asList(null, patientNoId), context, "Patient"); + List> resourceResults = new ArrayList<>(); + resourceResults.add(ResourceResult.builder().build()); + resourceResults.add(ResourceResult.builder().resource(patientNoId).build()); + Bundle responseBundle = helper.createSearchResponseBundle(resourceResults, context, "Patient"); // Validate results assertNotNull(responseBundle); diff --git a/fhir-smart/src/test/java/com/ibm/fhir/smart/test/MockPersistenceImpl.java b/fhir-smart/src/test/java/com/ibm/fhir/smart/test/MockPersistenceImpl.java index a94fd8f501a..c625a36d6d7 100644 --- a/fhir-smart/src/test/java/com/ibm/fhir/smart/test/MockPersistenceImpl.java +++ b/fhir-smart/src/test/java/com/ibm/fhir/smart/test/MockPersistenceImpl.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2017, 2021 + * (C) Copyright IBM Corp. 2017, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -145,12 +145,12 @@ public SingleResourceResult updateWithMeta(FHIRPersisten } @Override - public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { + public MultiResourceResult history(FHIRPersistenceContext context, Class resourceType, String logicalId) throws FHIRPersistenceException { return null; } @Override - public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { + public MultiResourceResult search(FHIRPersistenceContext context, Class resourceType) throws FHIRPersistenceException { return null; } diff --git a/operation/fhir-operation-erase/src/test/java/com/ibm/fhir/operation/erase/mock/MockFHIRResourceHelpers.java b/operation/fhir-operation-erase/src/test/java/com/ibm/fhir/operation/erase/mock/MockFHIRResourceHelpers.java index c00fc962501..b07dfd94fea 100644 --- a/operation/fhir-operation-erase/src/test/java/com/ibm/fhir/operation/erase/mock/MockFHIRResourceHelpers.java +++ b/operation/fhir-operation-erase/src/test/java/com/ibm/fhir/operation/erase/mock/MockFHIRResourceHelpers.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -177,7 +177,7 @@ public List doRetrieveIndex(FHIROperationContext operationContext, String @Override public Bundle doSearch(String type, String compartment, String compartmentId, MultivaluedMap queryParameters, String requestUri, - Resource contextResource, boolean checkIfInteractionAllowed) throws Exception { + Resource contextResource, boolean checkIfInteractionAllowed, boolean alwaysIncludeResource) throws Exception { return null; } diff --git a/operation/fhir-operation-member-match/src/test/java/com/ibm/fhir/operation/davinci/hrex/test/MemberMatchTest.java b/operation/fhir-operation-member-match/src/test/java/com/ibm/fhir/operation/davinci/hrex/test/MemberMatchTest.java index 3cbee74ccca..78e81afe7aa 100644 --- a/operation/fhir-operation-member-match/src/test/java/com/ibm/fhir/operation/davinci/hrex/test/MemberMatchTest.java +++ b/operation/fhir-operation-member-match/src/test/java/com/ibm/fhir/operation/davinci/hrex/test/MemberMatchTest.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2021 + * (C) Copyright IBM Corp. 2021, 2022 * * SPDX-License-Identifier: Apache-2.0 */ @@ -1551,12 +1551,12 @@ public LocalResourceHelpers(int responseBundleSize, int coverageBundleSize) { public Bundle doSearch(String type, String compartment, String compartmentId, MultivaluedMap queryParameters, String requestUri, Resource contextResource) throws Exception { // TODO Auto-generated method stub - return doSearch(type, compartment, compartmentId, queryParameters, requestUri, contextResource, false); + return doSearch(type, compartment, compartmentId, queryParameters, requestUri, contextResource, false, true); } @Override public Bundle doSearch(String type, String compartment, String compartmentId, MultivaluedMap queryParameters, String requestUri, - Resource contextResource, boolean checkIfInteractionAllowed) throws Exception { + Resource contextResource, boolean checkIfInteractionAllowed, boolean alwaysIncludeResource) throws Exception { if (throwException) { throw new Exception("Test"); }