diff --git a/core/client/pom.xml b/core/client/pom.xml index 910ce33..60bab47 100644 --- a/core/client/pom.xml +++ b/core/client/pom.xml @@ -126,5 +126,9 @@ org.slf4j slf4j-api + + javax.ws.rs + jsr311-api + diff --git a/core/client/src/main/java/org/phenotips/remote/client/RemoteMatchingService.java b/core/client/src/main/java/org/phenotips/remote/client/RemoteMatchingService.java index cecece4..15d32aa 100644 --- a/core/client/src/main/java/org/phenotips/remote/client/RemoteMatchingService.java +++ b/core/client/src/main/java/org/phenotips/remote/client/RemoteMatchingService.java @@ -17,6 +17,7 @@ */ package org.phenotips.remote.client; +import org.phenotips.matchingnotification.match.PatientMatch; import org.phenotips.remote.api.OutgoingMatchRequest; import org.phenotips.remote.common.internal.RemotePatientSimilarityView; @@ -34,7 +35,8 @@ @Role public interface RemoteMatchingService { - OutgoingMatchRequest sendRequest(String patientId, String remoteServerId, int addTopNGenes); + OutgoingMatchRequest sendRequest(String patientId, String remoteServerId, int addTopNGenes, + List matchesList); OutgoingMatchRequest getLastRequestSent(String patientId, String remoteServerId); diff --git a/core/client/src/main/java/org/phenotips/remote/client/internal/DefaultRemoteMatchingService.java b/core/client/src/main/java/org/phenotips/remote/client/internal/DefaultRemoteMatchingService.java index 2196f60..5674447 100644 --- a/core/client/src/main/java/org/phenotips/remote/client/internal/DefaultRemoteMatchingService.java +++ b/core/client/src/main/java/org/phenotips/remote/client/internal/DefaultRemoteMatchingService.java @@ -24,6 +24,7 @@ import org.phenotips.data.similarity.PatientSimilarityViewFactory; import org.phenotips.data.similarity.internal.DefaultAccessType; import org.phenotips.matchingnotification.MatchingNotificationManager; +import org.phenotips.matchingnotification.match.PatientMatch; import org.phenotips.remote.api.ApiConfiguration; import org.phenotips.remote.api.ApiDataConverter; import org.phenotips.remote.api.ApiViolationException; @@ -122,7 +123,8 @@ public class DefaultRemoteMatchingService implements RemoteMatchingService private MatchingNotificationManager notificationManager; @Override - public OutgoingMatchRequest sendRequest(String patientId, String remoteServerId, int addTopNGenes) + public OutgoingMatchRequest sendRequest(String patientId, String remoteServerId, int addTopNGenes, + List matchesList) { DefaultOutgoingMatchRequest request = new DefaultOutgoingMatchRequest(remoteServerId, ApiConfiguration.LATEST_API_VERSION_STRING, patientId); @@ -209,7 +211,8 @@ public OutgoingMatchRequest sendRequest(String patientId, String remoteServerId, if (ApiConfiguration.HTTP_OK.equals(httpStatus)) { List parsedResults = this.getSimilarityResults(request); - this.notificationManager.saveOutgoingMatches(parsedResults, patientId, request.getRemoteServerId()); + matchesList.addAll(this.notificationManager.saveOutgoingMatches(parsedResults, patientId, + request.getRemoteServerId())); } return request; diff --git a/core/client/src/main/java/org/phenotips/remote/client/internal/RemoteMatchFinder.java b/core/client/src/main/java/org/phenotips/remote/client/internal/RemoteMatchFinder.java index 45a5d0f..3ac821a 100644 --- a/core/client/src/main/java/org/phenotips/remote/client/internal/RemoteMatchFinder.java +++ b/core/client/src/main/java/org/phenotips/remote/client/internal/RemoteMatchFinder.java @@ -22,12 +22,11 @@ import org.phenotips.matchingnotification.finder.MatchFinder; import org.phenotips.matchingnotification.finder.internal.AbstractMatchFinder; import org.phenotips.matchingnotification.match.PatientMatch; -import org.phenotips.matchingnotification.match.internal.CurrentPatientMatch; +import org.phenotips.remote.api.ApiConfiguration; import org.phenotips.remote.api.OutgoingMatchRequest; import org.phenotips.remote.client.RemoteMatchingService; import org.phenotips.remote.common.ApplicationConfiguration; import org.phenotips.remote.common.RemoteConfigurationManager; -import org.phenotips.remote.common.internal.RemotePatientSimilarityView; import org.xwiki.component.annotation.Component; import org.xwiki.model.reference.DocumentReferenceResolver; @@ -41,6 +40,7 @@ import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; +import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; @@ -82,59 +82,65 @@ public int getPriority() } @Override - protected Set getSupportedServerIdList() + public Set getSupportedServerIdList() { return this.getRemotesList(); } @Override - protected MatchRunStatus specificFindMatches(Patient patient, String remoteId, List matchesList) + protected Response specificFindMatches(Patient patient, String remoteId, List matchesList) { - // Checking if a patient has a consent for remote matching - if (!this.consentManager.hasConsent(patient, REMOTE_MATCHING_CONSENT_ID)) { - this.logger.debug("Skipping patient {}. No consent for remote matching", patient.getId()); - return MatchRunStatus.NOT_RUN; - } + try { + // Checking if a patient has a consent for remote matching + if (!this.consentManager.hasConsent(patient, REMOTE_MATCHING_CONSENT_ID)) { + this.logger.debug("Skipping patient {}. No consent for remote matching", patient.getId()); + return Response.status(Response.Status.FORBIDDEN).build(); + } - this.logger.debug("Finding remote matches for patient [{}] on server [{}]", patient.getId(), remoteId); + this.logger.debug("Finding remote matches for patient [{}] on server [{}]", patient.getId(), remoteId); - OutgoingMatchRequest request = - this.matchingService.sendRequest(patient.getId(), remoteId, ADD_TOP_N_GENES_PARAMETER); + OutgoingMatchRequest remoteResponse = + this.matchingService.sendRequest(patient.getId(), remoteId, ADD_TOP_N_GENES_PARAMETER, matchesList); - MatchRunStatus status = checkRequestValidity(request, patient.getId(), remoteId); - if (status != MatchRunStatus.OK) { - return status; - } - - List parsedResults = this.matchingService.getSimilarityResults(request); - for (RemotePatientSimilarityView result : parsedResults) { - PatientMatch match = new CurrentPatientMatch(result, null, remoteId); - matchesList.add(match); - } - return MatchRunStatus.OK; - } + // If the response is null, the request was never initiated. + if (remoteResponse == null) { + this.logger.warn("Remote match request to [{}] was never initiated for patient [{}]", + remoteId, patient.getId()); + return Response.status(Response.Status.NO_CONTENT).build(); + } - private MatchRunStatus checkRequestValidity(OutgoingMatchRequest request, String patientId, String remoteId) - { - if (request != null && request.errorContactingRemoteServer()) { - this.logger.error("Unable to connect to remote server [{}] to send a request for patient [{}]", - remoteId, patientId); - return MatchRunStatus.ERROR; + if (!remoteResponse.wasSent()) { + if (remoteResponse.errorContactingRemoteServer()) { + this.logger.error("Unable to connect to remote server [{}]", remoteId); + return Response.status(Response.Status.SERVICE_UNAVAILABLE).build(); + } else { + this.logger.error("Could not initialte an MME match request for patient [{}]", patient.getId()); + return Response.status(Response.Status.CONFLICT).build(); + } + } + // If no valid reply, retrieve the request status code and the JSON. + if (!remoteResponse.gotValidReply()) { + if (remoteResponse.getRequestStatusCode().equals(ApiConfiguration.HTTP_UNAUTHORIZED)) { + this.logger.error("Not authorized to contact selected MME server [{}]", remoteId); + return Response.status(Response.Status.UNAUTHORIZED).build(); + } + if (remoteResponse.getRequestStatusCode().equals(ApiConfiguration.HTTP_UNSUPPORTED_API_VERSION)) { + this.logger.error("Unsupported MME version when contacting MME server [{}]", remoteId); + return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).build(); + } + this.logger.error("Remote MME server [{}] rejected match request with status code [{}]", + remoteId, remoteResponse.getRequestStatusCode()); + this.logger.error(" ...and error details: [{}]", remoteResponse.getResponseJSON()); + return Response.status(Response.Status.NOT_ACCEPTABLE).build(); } - if (request == null || !request.wasSent()) { - this.logger.error("Request for patientId [{}] was not sent to server [{}]", patientId, remoteId); - return MatchRunStatus.NOT_RUN; - } + return Response.status(Response.Status.OK).build(); - if (!request.gotValidReply()) { - this.logger.error("Request for patientId {}, remoteId {} returned with status code: {}", - patientId, remoteId, request.getRequestStatusCode()); - this.logger.error(" ...and error details: [{}]", request.getResponseJSON()); - return MatchRunStatus.ERROR; + } catch (final Exception e) { + this.logger.error("Unexpected exception while generating remote matches: {}", e.getMessage()); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } - return MatchRunStatus.OK; } private Set getRemotesList() diff --git a/core/pom.xml b/core/pom.xml index bc550ea..05c60f7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -35,7 +35,6 @@ common server client - rest metrics diff --git a/core/rest/pom.xml b/core/rest/pom.xml deleted file mode 100644 index fb9d7f6..0000000 --- a/core/rest/pom.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - 4.0.0 - - org.phenotips - remote-matching-core - 1.3-SNAPSHOT - - - 0.94 - - remote-matching-core-rest - Remote Matching - Core - REST - - - com.google.code.findbugs - jsr305 - - - org.xwiki.platform - xwiki-platform-store-api - ${xwiki.version} - - - org.xwiki.platform - xwiki-platform-container-api - ${xwiki.version} - - - org.xwiki.platform - xwiki-platform-rest-server - ${xwiki.version} - - - org.xwiki.commons - xwiki-commons-component-api - ${xwiki.version} - - - org.xwiki.commons - xwiki-commons-context - ${xwiki.version} - - - org.xwiki.platform - xwiki-platform-oldcore - ${xwiki.version} - - - org.xwiki.platform - xwiki-platform-model - ${xwiki.version} - - - ${project.groupId} - remote-matching-core-api - ${project.version} - - - ${project.groupId} - remote-matching-core-common - ${project.version} - - - ${project.groupId} - remote-matching-core-client - ${project.version} - - - ${project.groupId} - matching-notification-api - ${patientNetwork.version} - - - ${project.groupId} - patient-data-api - ${phenotips.version} - - - ${project.groupId} - patient-similarity-data-impl - ${patientNetwork.version} - - - ${project.groupId} - phenotips-rest-commons - ${phenotips.version} - - - ${project.groupId} - patient-data-rest - ${phenotips.version} - - - ${project.groupId} - patient-similarity-data-api - ${patientNetwork.version} - - - javax.ws.rs - jsr311-api - - - org.json - json - - - org.slf4j - slf4j-api - - - org.apache.commons - commons-lang3 - - - - org.xwiki.commons - xwiki-commons-tool-test-component - ${xwiki.version} - test - - - javax.servlet - javax.servlet-api - test - - - diff --git a/core/rest/src/main/java/org/phenotips/remote/rest/RemotePatientMatchResource.java b/core/rest/src/main/java/org/phenotips/remote/rest/RemotePatientMatchResource.java deleted file mode 100644 index 083f4c6..0000000 --- a/core/rest/src/main/java/org/phenotips/remote/rest/RemotePatientMatchResource.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ - */ -package org.phenotips.remote.rest; - -import org.phenotips.data.rest.PatientResource; -import org.phenotips.rest.ParentResource; -import org.phenotips.rest.Relation; - -import org.xwiki.stability.Unstable; - -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -/** - * Resource for working with remote similar cases data. - * - * @version $Id$ - * @since 1.1 - */ -@Unstable("New API introduced in 1.1") -@Path("/patients/{entity-id}/similar-remote-cases") -@Relation("https://phenotips.org/rel/patientSimilarityRemote") -@ParentResource(PatientResource.class) -public interface RemotePatientMatchResource -{ - /** - * Finds matching patients for a provided reference patient. The following additional parameters may be - * specified: - * - *
- *
patientId
- *
the internal identifier for a local reference patient; must be specified
- *
offset
- *
the offset for the returned match data (one-based), must be an int; default value is set to 1
- *
maxResults
- *
the maximum number of matches to return, must be an int; default is -1, which signifies "all matches"
- *
reqNo
- *
the request number, must be an integer; default value is set to 1
- *
server
- *
the remote server to be queried for matching patients; must be specified
- *
sendNewRequest
- *
true iff a new request should be sent to the remote matching server; default value is false
- *
- * - * @param patientId the identifier of the patient to fetch matches for - * @return a response containing the reference patient and matched patients data, or an error code if unsuccessful - */ - @POST - @Produces(MediaType.APPLICATION_JSON) - Response findRemoteMatchingPatients(@PathParam("entity-id") String patientId); -} diff --git a/core/rest/src/main/java/org/phenotips/remote/rest/internal/DefaultRemotePatientMatchResource.java b/core/rest/src/main/java/org/phenotips/remote/rest/internal/DefaultRemotePatientMatchResource.java deleted file mode 100644 index d2a1b29..0000000 --- a/core/rest/src/main/java/org/phenotips/remote/rest/internal/DefaultRemotePatientMatchResource.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ - */ -package org.phenotips.remote.rest.internal; - -import org.phenotips.data.Patient; -import org.phenotips.data.PatientRepository; -import org.phenotips.data.similarity.MatchedPatientClusterView; -import org.phenotips.data.similarity.PatientSimilarityView; -import org.phenotips.matchingnotification.match.PatientMatch; -import org.phenotips.matchingnotification.storage.MatchStorageManager; -import org.phenotips.remote.api.ApiConfiguration; -import org.phenotips.remote.api.OutgoingMatchRequest; -import org.phenotips.remote.client.RemoteMatchingService; -import org.phenotips.remote.common.internal.RemotePatientSimilarityView; -import org.phenotips.remote.rest.RemotePatientMatchResource; -import org.xwiki.component.annotation.Component; -import org.xwiki.container.Container; -import org.xwiki.container.Request; -import org.xwiki.rest.XWikiResource; - -import java.util.List; -import java.util.Map; - -import javax.annotation.Nonnull; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.json.JSONObject; - -/** - * Default implementation of the {@link RemotePatientMatchResource}. - * - * @version $Id$ - * @since 1.1 - */ -@Component -@Named("org.phenotips.remote.rest.internal.DefaultRemotePatientMatchResource") -@Singleton -public class DefaultRemotePatientMatchResource extends XWikiResource implements RemotePatientMatchResource -{ - private static final String REQ_NO = "reqNo"; - - private static final String OFFSET = "offset"; - - private static final String LIMIT = "maxResults"; - - private static final String SERVER = "server"; - - private static final String SEND_NEW_REQUEST = "sendNewRequest"; - - /** The secure patient repository. */ - @Inject - @Named("secure") - private PatientRepository repository; - - /** The remote similar patients matching service. */ - @Inject - private RemoteMatchingService matchingService; - - /** The XWiki container. */ - @Inject - private Container container; - - @Inject - private MatchStorageManager matchStorageManager; - - @Override - public Response findRemoteMatchingPatients(final String patientId) - { - // Get the request container. - final Request request = this.container.getRequest(); - // The patient ID must not be blank. - if (StringUtils.isBlank(patientId)) { - this.slf4Jlogger.error("Patient ID is not specified."); - return Response.status(Response.Status.BAD_REQUEST).build(); - } - // The server must not be blank. - final String server = (String) request.getProperty(SERVER); - if (StringUtils.isBlank(server)) { - this.slf4Jlogger.error("Server is not specified."); - return Response.status(Response.Status.BAD_REQUEST).build(); - } - // Get the other parameters, if specified, or set the defaults. - final int offset = NumberUtils.toInt((String) request.getProperty(OFFSET), 1); - if (offset < 1) { - this.slf4Jlogger.error("The requested offset is out of bounds: {}", offset); - return Response.status(Response.Status.BAD_REQUEST).build(); - } - final boolean newRequest = BooleanUtils.toBoolean((String) request.getProperty(SEND_NEW_REQUEST)); - final int limit = NumberUtils.toInt((String) request.getProperty(LIMIT), -1); - final int reqNo = NumberUtils.toInt((String) request.getProperty(REQ_NO), 1); - // Build the response. - return buildResponse(patientId, server, offset, limit, newRequest, reqNo); - } - - /** - * Builds a response containing the reference patient and matched patients data, or an error code if unsuccessful. - * - * @param patientId the local reference patient identifier - * @param server the remote server that will be queried for matches - * @param offset the offset for the returned matches - * @param limit the maximum number of matches to return after the offset - * @param newRequest true iff a new match request should be made to the remote server - * @param reqNo the current request number - * @return a {@link Response} containing the matched patients data, or an error code if unsuccessful - */ - @SuppressWarnings("ReturnCount") - private Response buildResponse( - @Nonnull final String patientId, - @Nonnull final String server, - final int offset, - final int limit, - final boolean newRequest, - final int reqNo) - { - try { - // Checks if the current user has access to the requested patient. - final Patient patient = this.repository.get(patientId); - // If patient with requested ID is not found, this is an error. - if (patient == null) { - this.slf4Jlogger.error("Patient with ID: {} could not be found.", patientId); - return Response.status(Response.Status.BAD_REQUEST).build(); - } - // don't send any top Exomiser genes in outgoing requests - final OutgoingMatchRequest remoteResponse = newRequest - ? this.matchingService.sendRequest(patientId, server, 0) - : this.matchingService.getLastRequestSent(patientId, server); - - // If the response is null, the request was never initiated. - if (remoteResponse == null) { - this.slf4Jlogger.warn("Remote match request to [{}] was never initiated for patient [{}]", - server, patientId); - return Response.status(Response.Status.NO_CONTENT).build(); - } - - if (!remoteResponse.wasSent()) { - if (remoteResponse.errorContactingRemoteServer()) { - this.slf4Jlogger.error("Unable to connect to remote server [{}]", server); - return Response.status(Response.Status.SERVICE_UNAVAILABLE).build(); - } else { - this.slf4Jlogger.error("Could not initialte an MME match request for patient [{}]", patientId); - return Response.status(Response.Status.CONFLICT).build(); - } - } - // If no valid reply, retrieve the request status code and the JSON. - if (!remoteResponse.gotValidReply()) { - if (remoteResponse.getRequestStatusCode().equals(ApiConfiguration.HTTP_UNAUTHORIZED)) { - this.slf4Jlogger.error("Not authorized to contact selected MME server [{}]", server); - return Response.status(Response.Status.FORBIDDEN).build(); - } - if (remoteResponse.getRequestStatusCode().equals(ApiConfiguration.HTTP_UNSUPPORTED_API_VERSION)) { - this.slf4Jlogger.error("Unsupported MME version when contacting MME server [{}]", server); - return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE).build(); - } - this.slf4Jlogger.error("Remote MME server [{}] rejected match request with status code [{}]", - server, remoteResponse.getRequestStatusCode()); - return Response.status(Response.Status.NOT_ACCEPTABLE).build(); - } - - return buildMatches(patient, remoteResponse, offset, limit, reqNo); - } catch (final SecurityException e) { - this.slf4Jlogger.error("Failed to retrieve patient with ID [{}]: {}", patientId, e.getMessage()); - return Response.status(Response.Status.UNAUTHORIZED).build(); - } catch (final IndexOutOfBoundsException e) { - this.slf4Jlogger.error("The requested offset [{}] is out of bounds", offset); - return Response.status(Response.Status.BAD_REQUEST).build(); - } catch (final Exception e) { - this.slf4Jlogger.error("Unexpected exception while generating remote matches: {}", e.getMessage()); - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); - } - } - - /** - * Builds a response containing the reference patient and matched patients data. - * - * @param patient the reference {@link Patient} object - * @param mmeMatchRequest the response received from the remote server - * @param offset the offset for the returned matches - * @param limit the maximum number of matches to return after the offset - * @param reqNo the current request number - * @return a {@link Response} containing the matched patients data - */ - private Response buildMatches( - @Nonnull final Patient patient, - @Nonnull final OutgoingMatchRequest mmeMatchRequest, - final int offset, - final int limit, - final int reqNo) - { - final List matches = this.matchingService.getSimilarityResults(mmeMatchRequest); - - // FIXME saveRemoteMatches() is the most reliable way to get PatientMatch ids corresponding to - // SimilarityViews. However this is inefficient (though with latest Pn code supposedly nothing will - // be written to the DB if matches do not change), and in the future we need to combine - // SimilarityView and PatientMatch and operate on a single entity, which would be obtained from - // a single table. - - final Map matchMapping = this.matchStorageManager.saveRemoteMatches( - matches, patient.getId(), mmeMatchRequest.getRemoteServerId(), false); - - final MatchedPatientClusterView matchedCluster = - new RemoteMatchedPatientClusterView(patient, mmeMatchRequest, matches, matchMapping); - - final JSONObject matchesJson = matchedCluster.toJSON(offset - 1, limit); - matchesJson.put(REQ_NO, reqNo); - return Response.ok(matchesJson, MediaType.APPLICATION_JSON_TYPE).build(); - } -} diff --git a/core/rest/src/main/java/org/phenotips/remote/rest/internal/RemoteMatchedPatientClusterView.java b/core/rest/src/main/java/org/phenotips/remote/rest/internal/RemoteMatchedPatientClusterView.java deleted file mode 100644 index 9df9a88..0000000 --- a/core/rest/src/main/java/org/phenotips/remote/rest/internal/RemoteMatchedPatientClusterView.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ - */ -package org.phenotips.remote.rest.internal; - -import org.phenotips.data.Patient; -import org.phenotips.data.similarity.MatchedPatientClusterView; -import org.phenotips.data.similarity.PatientSimilarityView; -import org.phenotips.data.similarity.internal.DefaultMatchedPatientClusterView; -import org.phenotips.matchingnotification.match.PatientMatch; -import org.phenotips.remote.api.OutgoingMatchRequest; -import org.phenotips.remote.common.internal.RemotePatientSimilarityView; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.apache.commons.lang3.Validate; -import org.json.JSONObject; - -/** - * Remote matches implementation of {@link MatchedPatientClusterView} that provides access to the reference patient and - * its remote matches. - */ -public class RemoteMatchedPatientClusterView extends DefaultMatchedPatientClusterView implements MatchedPatientClusterView -{ - private static final String RESPONSE = "response"; - - private static final String REQUEST = "request"; - - /** The response from remote server. */ - private final OutgoingMatchRequest mmeMatchRequest; - - /** - * Default constructor that takes a local reference {@code patient}, and its {@code remoteMatches remote matches}. - * - * @param patient the local reference {@code patient} - * @param response the response from the remote server, as {@link OutgoingMatchRequest} - * @param remoteMatches a list of {@link PatientSimilarityView} objects representing remote matching patients - * @param matchesIds a map of {@link PatientSimilarityView} objects to the IDs of corresponding matches saved in DB - */ - public RemoteMatchedPatientClusterView( - @Nonnull final Patient patient, - @Nonnull final OutgoingMatchRequest mmeMatchRequest, - @Nullable final List remoteMatches, - @Nullable final Map matchesIds) - { - super(patient, remoteMatches, matchesIds); - - Validate.notNull(mmeMatchRequest, "The remote response must not be null."); - this.mmeMatchRequest = mmeMatchRequest; - } - - @Override - public JSONObject toJSON(final int fromIndex, final int maxResults) - throws IndexOutOfBoundsException - { - JSONObject result = super.toJSON(fromIndex, maxResults); - result.put(REQUEST, this.mmeMatchRequest.getRequestJSON()); - result.put(RESPONSE, this.mmeMatchRequest.getResponseJSON()); - return result; - } - - @Override - public boolean equals(@Nullable final Object o) - { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final RemoteMatchedPatientClusterView that = (RemoteMatchedPatientClusterView) o; - return Objects.equals(this.mmeMatchRequest, that.mmeMatchRequest) && super.equals(that); - } - - @Override - public int hashCode() - { - return Objects.hash(this.mmeMatchRequest, super.hashCode()); - } -} diff --git a/core/rest/src/main/resources/META-INF/components.txt b/core/rest/src/main/resources/META-INF/components.txt deleted file mode 100644 index 098a595..0000000 --- a/core/rest/src/main/resources/META-INF/components.txt +++ /dev/null @@ -1 +0,0 @@ -org.phenotips.remote.rest.internal.DefaultRemotePatientMatchResource diff --git a/core/rest/src/test/java/org/phenotips/remote/rest/internal/DefaultRemotePatientMatchResourceTest.java b/core/rest/src/test/java/org/phenotips/remote/rest/internal/DefaultRemotePatientMatchResourceTest.java deleted file mode 100644 index fd270a3..0000000 --- a/core/rest/src/test/java/org/phenotips/remote/rest/internal/DefaultRemotePatientMatchResourceTest.java +++ /dev/null @@ -1,486 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ - */ -package org.phenotips.remote.rest.internal; - -import org.phenotips.data.Patient; -import org.phenotips.data.PatientRepository; -import org.phenotips.remote.api.ApiConfiguration; -import org.phenotips.remote.api.OutgoingMatchRequest; -import org.phenotips.remote.client.RemoteMatchingService; -import org.phenotips.remote.common.internal.RemotePatientSimilarityView; -import org.phenotips.remote.rest.RemotePatientMatchResource; -import org.xwiki.component.manager.ComponentLookupException; -import org.xwiki.component.manager.ComponentManager; -import org.xwiki.container.Container; -import org.xwiki.context.Execution; -import org.xwiki.context.ExecutionContext; -import org.xwiki.store.UnexpectedException; -import org.xwiki.test.mockito.MockitoComponentMockingRule; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.inject.Provider; -import javax.ws.rs.core.Response; - -import org.apache.commons.lang3.StringUtils; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.slf4j.Logger; - -import com.xpn.xwiki.XWikiContext; - -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * Unit tests for {@link DefaultRemotePatientMatchResource}. - */ -public class DefaultRemotePatientMatchResourceTest -{ - private static final String REFERENCE = "reference"; - - private static final String MATCH_1 = "match1"; - - private static final String MATCH_2 = "match2"; - - private static final String MATCH_3 = "match3"; - - private static final String SECURE = "secure"; - - private static final String ID = "id"; - - private static final String QUERY = "query"; - - private static final String TOTAL_SIZE = "resultsCount"; - - private static final String RETURNED_SIZE = "returnedCount"; - - private static final String RESULTS = "results"; - - private static final String REQ_NO = "reqNo"; - - private static final String UNAUTHORIZED_MSG = "User unauthorized"; - - private static final String UNEXPECTED_MSG = "Unexpected exception"; - - private static final String LIMIT = "maxResults"; - - private static final String OFFSET = "offset"; - - private static final String REMOTE_SERVER = "remoteServer"; - - private static final String REQUEST_JSON = "requestJson"; - - private static final String RESPONSE_JSON = "responseJson"; - - private static final String REQUEST = "request"; - - private static final String RESPONSE = "response"; - - private static final String SEND_NEW_REQUEST = "sendNewRequest"; - - private static final String SERVER = "server"; - - private static final String ERROR = "error"; - - @Rule - public MockitoComponentMockingRule mocker = - new MockitoComponentMockingRule(DefaultRemotePatientMatchResource.class); - - @Mock - private Patient reference; - - @Mock - private OutgoingMatchRequest response; - - @Mock - private RemotePatientSimilarityView match1; - - @Mock - private RemotePatientSimilarityView match2; - - @Mock - private RemotePatientSimilarityView match3; - - @Mock - private org.xwiki.container.Request request; - - private RemotePatientMatchResource component; - - private Logger logger; - - private PatientRepository repository; - - private RemoteMatchingService matchingService; - - private JSONObject expectedAll; - - @Before - public void setUp() throws ComponentLookupException - { - MockitoAnnotations.initMocks(this); - final Execution execution = mock(Execution.class); - final ExecutionContext executionContext = mock(ExecutionContext.class); - final ComponentManager compManager = this.mocker.getInstance(ComponentManager.class, "context"); - final Provider provider = this.mocker.getInstance(XWikiContext.TYPE_PROVIDER); - final XWikiContext context = provider.get(); - when(compManager.getInstance(Execution.class)).thenReturn(execution); - when(execution.getContext()).thenReturn(executionContext); - when(executionContext.getProperty("xwikicontext")).thenReturn(context); - - this.component = this.mocker.getComponentUnderTest(); - // Set up all injected classes. - this.logger = this.mocker.getMockedLogger(); - this.repository = this.mocker.getInstance(PatientRepository.class, SECURE); - this.matchingService = this.mocker.getInstance(RemoteMatchingService.class); - - // Mock the reference patient. - when(this.repository.get(REFERENCE)).thenReturn(this.reference); - when(this.reference.toJSON()).thenReturn(new JSONObject().put(ID, REFERENCE)); - - // Mock matches search. - final List matches = Arrays.asList(this.match1, this.match2, this.match3); - when(this.matchingService.sendRequest(REFERENCE, REMOTE_SERVER, 0)).thenReturn(this.response); - when(this.matchingService.getLastRequestSent(REFERENCE, REMOTE_SERVER)).thenReturn(this.response); - when(this.matchingService.getSimilarityResults(this.response)).thenReturn(matches); - - // Mock response interactions. - when(this.response.getRequestJSON()).thenReturn(new JSONObject().put(REQUEST_JSON, REQUEST_JSON)); - when(this.response.getResponseJSON()).thenReturn(new JSONObject().put(RESPONSE_JSON, RESPONSE_JSON)); - when(this.response.gotValidReply()).thenReturn(true); - when(this.response.wasSent()).thenReturn(true); - - final Container container = this.mocker.getInstance(Container.class); - // Mock container interactions. - when(container.getRequest()).thenReturn(this.request); - - // Mock request data. - when(this.request.getProperty(SERVER)).thenReturn(REMOTE_SERVER); - when(this.request.getProperty(SEND_NEW_REQUEST)).thenReturn("false"); - when(this.request.getProperty(OFFSET)).thenReturn("1"); - when(this.request.getProperty(LIMIT)).thenReturn("10"); - when(this.request.getProperty(REQ_NO)).thenReturn("1"); - - // Mock individual match data. - when(this.match1.toJSON()).thenReturn(new JSONObject().put(ID, MATCH_1)); - when(this.match2.toJSON()).thenReturn(new JSONObject().put(ID, MATCH_2)); - when(this.match3.toJSON()).thenReturn(new JSONObject().put(ID, MATCH_3)); - - this.expectedAll = constructAllMatchesJSON(); - } - - @Test - public void findRemoteMatchingPatientsNullPatientIdResultsInBadRequest() - { - final Response response = this.component.findRemoteMatchingPatients(null); - verify(this.logger).error("Patient ID is not specified."); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsEmptyPatientIdResultsInBadRequest() - { - final Response response = this.component.findRemoteMatchingPatients(StringUtils.EMPTY); - verify(this.logger).error("Patient ID is not specified."); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsBlankPatientIdResultsInBadRequest() - { - final Response response = this.component.findRemoteMatchingPatients(StringUtils.SPACE); - verify(this.logger).error("Patient ID is not specified."); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsNullServerResultsInBadRequest() - { - when(this.request.getProperty(SERVER)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Server is not specified."); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsEmptyServerResultsInBadRequest() - { - when(this.request.getProperty(SERVER)).thenReturn(StringUtils.EMPTY); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Server is not specified."); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsBlankServerResultsInBadRequest() - { - when(this.request.getProperty(SERVER)).thenReturn(StringUtils.SPACE); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Server is not specified."); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsPatientDoesNotExistResultsInBadRequest() - { - when(this.repository.get(REFERENCE)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Patient with ID: {} could not be found.", REFERENCE); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsUserNotAuthorizedToSeeReferencePatientResultsInUnauthorized() - { - when(this.repository.get(REFERENCE)).thenThrow(new SecurityException(UNAUTHORIZED_MSG)); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Failed to retrieve patient with ID [{}]: {}", REFERENCE, UNAUTHORIZED_MSG); - Assert.assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientRetrieveOldRequestButNoneStoredResultsInNoContent() - { - when(this.matchingService.getLastRequestSent(REFERENCE, REMOTE_SERVER)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).warn("Remote match request to [{}] was never initiated for patient [{}]", - REMOTE_SERVER, REFERENCE); - Assert.assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientReturnsProperErrorWhenFailedToConnectToRemoteServer() - { - final String message = "I'm not valid."; - final JSONObject errJSON = new JSONObject().put(ERROR, message); - when(this.response.gotValidReply()).thenReturn(false); - when(this.response.wasSent()).thenReturn(false); - when(this.response.errorContactingRemoteServer()).thenReturn(true); - when(this.response.getRequestJSON()).thenReturn(errJSON); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Unable to connect to remote server [{}]", REMOTE_SERVER); - Assert.assertEquals(Response.Status.SERVICE_UNAVAILABLE.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientReturnsProperErrorWhenNotAuthorizedOnRemoteServer() - { - final String message = "I'm not valid."; - final JSONObject errJSON = new JSONObject().put(ERROR, message); - when(this.response.gotValidReply()).thenReturn(false); - when(this.response.getRequestStatusCode()).thenReturn(ApiConfiguration.HTTP_UNAUTHORIZED); - when(this.response.getRequestJSON()).thenReturn(errJSON); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Not authorized to contact selected MME server [{}]", REMOTE_SERVER); - Assert.assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientReturnsProperErrorWhenRetrieveOldNonvalidRequest() - { - final String message = "I'm not valid."; - final JSONObject errJSON = new JSONObject().put(ERROR, message); - when(this.response.gotValidReply()).thenReturn(false); - when(this.response.getRequestStatusCode()).thenReturn(-1); - when(this.response.getRequestJSON()).thenReturn(errJSON); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Remote MME server [{}] rejected match request with status code [{}]", - REMOTE_SERVER, -1); - Assert.assertEquals(Response.Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsNoMatchesFoundResultsInValidResponse() - { - final List matches = Collections.emptyList(); - when(this.matchingService.getSimilarityResults(this.response)).thenReturn(matches); - final JSONObject expected = new JSONObject() - .put(QUERY, new JSONObject() - .put(ID, REFERENCE)) - .put(TOTAL_SIZE, 0) - .put(REQUEST, this.response.getRequestJSON()) - .put(RESPONSE, this.response.getResponseJSON()) - .put(RETURNED_SIZE, 0) - .put(REQ_NO, 1) - .put(OFFSET, 1) - .put(RESULTS, new JSONArray()); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(expected.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsLessThanOneOffsetResultsInBadRequest() - { - when(this.request.getProperty(OFFSET)).thenReturn("-1"); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("The requested offset is out of bounds: {}", -1); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsTooLargeOffsetResultsInBadRequest() - { - when(this.request.getProperty(OFFSET)).thenReturn("60"); - when(this.request.getProperty(LIMIT)).thenReturn("80"); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("The requested offset [{}] is out of bounds", 60); - Assert.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - - @Test - public void findRemoteMatchingPatientsOffsetNullDefaultsToOne() - { - when(this.request.getProperty(OFFSET)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(this.expectedAll.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsLimitNullDefaultsToNegativeOne() - { - when(this.request.getProperty(LIMIT)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(this.expectedAll.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsReqNoNullDefaultsToOne() - { - when(this.request.getProperty(REQ_NO)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(this.expectedAll.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsSendNewRequestNullDefaultsToFalse() - { - when(this.request.getProperty(SEND_NEW_REQUEST)).thenReturn(null); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.matchingService, never()).sendRequest(anyString(), anyString(), anyInt()); - verify(this.matchingService, times(1)).getLastRequestSent(REFERENCE, REMOTE_SERVER); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(this.expectedAll.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsSendNewRequestTrueWorksAsExpected() - { - when(this.request.getProperty(SEND_NEW_REQUEST)).thenReturn("true"); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.matchingService, times(1)).sendRequest(REFERENCE, REMOTE_SERVER, 0); - verify(this.matchingService, never()).getLastRequestSent(anyString(), anyString()); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(this.expectedAll.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsLimitBiggerThanMatchesNumberReturnsAllMatchesFromOffset() - { - when(this.request.getProperty(LIMIT)).thenReturn("80"); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(this.expectedAll.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsLimitNegativeReturnsAllMatchesFromOffset() - { - when(this.request.getProperty(OFFSET)).thenReturn("2"); - when(this.request.getProperty(LIMIT)).thenReturn("-1"); - final JSONObject expected = new JSONObject() - .put(QUERY, new JSONObject() - .put(ID, REFERENCE)) - .put(TOTAL_SIZE, 3) - .put(REQUEST, this.response.getRequestJSON()) - .put(RESPONSE, this.response.getResponseJSON()) - .put(RETURNED_SIZE, 2) - .put(REQ_NO, 1) - .put(OFFSET, 2) - .put(RESULTS, new JSONArray() - .put(this.match2.toJSON()) - .put(this.match3.toJSON())); - - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(expected.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsLimitLessThanLastResultReturnsCorrectSubset() - { - when(this.request.getProperty(OFFSET)).thenReturn("2"); - when(this.request.getProperty(LIMIT)).thenReturn("1"); - final JSONObject expected = new JSONObject() - .put(QUERY, new JSONObject() - .put(ID, REFERENCE)) - .put(TOTAL_SIZE, 3) - .put(REQUEST, this.response.getRequestJSON()) - .put(RESPONSE, this.response.getResponseJSON()) - .put(RETURNED_SIZE, 1) - .put(REQ_NO, 1) - .put(OFFSET, 2) - .put(RESULTS, new JSONArray() - .put(this.match2.toJSON())); - - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Assert.assertTrue(expected.similar(response.getEntity())); - } - - @Test - public void findRemoteMatchingPatientsUnexpectedExceptionIsThrown() - { - when(this.repository.get(REFERENCE)).thenThrow(new UnexpectedException(UNEXPECTED_MSG)); - final Response response = this.component.findRemoteMatchingPatients(REFERENCE); - verify(this.logger).error("Unexpected exception while generating remote matches: {}", UNEXPECTED_MSG); - Assert.assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); - } - private JSONObject constructAllMatchesJSON() - { - return new JSONObject() - .put(QUERY, new JSONObject() - .put(ID, REFERENCE)) - .put(TOTAL_SIZE, 3) - .put(REQUEST, this.response.getRequestJSON()) - .put(RESPONSE, this.response.getResponseJSON()) - .put(RETURNED_SIZE, 3) - .put(REQ_NO, 1) - .put(OFFSET, 1) - .put(RESULTS, new JSONArray() - .put(this.match1.toJSON()) - .put(this.match2.toJSON()) - .put(this.match3.toJSON())); - } -} diff --git a/core/rest/src/test/java/org/phenotips/remote/rest/internal/RemoteMatchedPatientClusterViewTest.java b/core/rest/src/test/java/org/phenotips/remote/rest/internal/RemoteMatchedPatientClusterViewTest.java deleted file mode 100644 index 21a98b6..0000000 --- a/core/rest/src/test/java/org/phenotips/remote/rest/internal/RemoteMatchedPatientClusterViewTest.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see http://www.gnu.org/licenses/ - */ -package org.phenotips.remote.rest.internal; - -import org.phenotips.data.Patient; -import org.phenotips.data.similarity.MatchedPatientClusterView; -import org.phenotips.data.similarity.PatientSimilarityView; -import org.phenotips.remote.api.OutgoingMatchRequest; -import org.phenotips.remote.common.internal.RemotePatientSimilarityView; - -import org.xwiki.model.reference.DocumentReference; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Unit tests for {@link RemoteMatchedPatientClusterView}. - */ -public class RemoteMatchedPatientClusterViewTest -{ - private static final String ID_LABEL = "id"; - - private static final String REFERENCE = "reference"; - - private static final String PATIENT_1 = "patient1"; - - private static final String PATIENT_2 = "patient2"; - - private static final String PATIENT_3 = "patient3"; - - private static final String PATIENT_4 = "patient4"; - - private static final String PATIENT_5 = "patient5"; - - private static final String QUERY_LABEL = "query"; - - private static final String TOTAL_LABEL = "resultsCount"; - - private static final String RETURNED_LABEL = "returnedCount"; - - private static final String RESULTS_LABEL = "results"; - - private static final String OFFSET_LABEL = "offset"; - - private static final String REQUEST_JSON = "requestJSON"; - - private static final String RESPONSE_JSON = "responseJSON"; - - private static final String REQUEST = "request"; - - private static final String RESPONSE = "response"; - - @Mock - private Patient reference; - - @Mock - private DocumentReference docRef; - - @Mock - private RemotePatientSimilarityView patient1; - - @Mock - private DocumentReference doc1; - - @Mock - private RemotePatientSimilarityView patient2; - - @Mock - private DocumentReference doc2; - - @Mock - private RemotePatientSimilarityView patient3; - - @Mock - private DocumentReference doc3; - - @Mock - private RemotePatientSimilarityView patient4; - - @Mock - private DocumentReference doc4; - - @Mock - private RemotePatientSimilarityView patient5; - - @Mock - private DocumentReference doc5; - - @Mock - private OutgoingMatchRequest response; - - private RemoteMatchedPatientClusterView matches; - - private List matchList; - - @Before - public void setUp() - { - MockitoAnnotations.initMocks(this); - - when(this.reference.toJSON()).thenReturn(new JSONObject().put(ID_LABEL, REFERENCE)); - when(this.reference.getDocumentReference()).thenReturn(this.docRef); - - when(this.patient1.toJSON()).thenReturn(new JSONObject().put(ID_LABEL, PATIENT_1)); - when(this.patient1.getDocumentReference()).thenReturn(this.doc1); - - when(this.patient2.toJSON()).thenReturn(new JSONObject().put(ID_LABEL, PATIENT_2)); - when(this.patient2.getDocumentReference()).thenReturn(this.doc2); - - when(this.patient3.toJSON()).thenReturn(new JSONObject().put(ID_LABEL, PATIENT_3)); - when(this.patient3.getDocumentReference()).thenReturn(this.doc3); - - when(this.patient4.toJSON()).thenReturn(new JSONObject().put(ID_LABEL, PATIENT_4)); - when(this.patient4.getDocumentReference()).thenReturn(this.doc4); - - when(this.patient5.toJSON()).thenReturn(new JSONObject().put(ID_LABEL, PATIENT_5)); - when(this.patient5.getDocumentReference()).thenReturn(this.doc5); - - when(this.response.getRequestJSON()).thenReturn(new JSONObject().put(REQUEST_JSON, REQUEST_JSON)); - when(this.response.getResponseJSON()).thenReturn(new JSONObject().put(RESPONSE_JSON, RESPONSE_JSON)); - - this.matchList = Arrays.asList(this.patient1, this.patient2, this.patient3, this.patient4, this.patient5); - this.matches = new RemoteMatchedPatientClusterView(this.reference, this.response, this.matchList, null); - } - - @Test(expected = NullPointerException.class) - public void instantiatingClassWithNullPatientThrowsException() - { - new RemoteMatchedPatientClusterView(null, this.response, this.matchList, null); - } - - @Test(expected = NullPointerException.class) - public void instantiatingClassWithNullResponseThrowsException() - { - new RemoteMatchedPatientClusterView(this.reference, null, this.matchList, null); - } - - @Test - public void getReferenceReturnsTheReferenceThatWasSet() - { - Assert.assertEquals(this.reference, this.matches.getReference()); - } - - @Test - public void getMatchesReturnsEmptyListIfNoMatchesSet() - { - final MatchedPatientClusterView matches = new RemoteMatchedPatientClusterView(this.reference, this.response, - Collections.emptyList(), null); - Assert.assertTrue(matches.getMatches().isEmpty()); - } - - @Test - public void getMatchesReturnsTheMatchesThatWereProvided() - { - Assert.assertEquals(this.matchList, this.matches.getMatches()); - } - - @Test(expected = UnsupportedOperationException.class) - public void getMatchesReturnedMatchesCannotBeModified() - { - this.matches.getMatches().add(mock(PatientSimilarityView.class)); - } - - @Test - public void sizeIsZeroIfMatchesIsEmpty() - { - final MatchedPatientClusterView matches = new RemoteMatchedPatientClusterView(this.reference, this.response, - Collections.emptyList(), null); - Assert.assertEquals(0, matches.size()); - } - - @Test - public void sizeReturnsCorrectNumberOfMatches() - { - Assert.assertEquals(5, this.matches.size()); - } - - @Test(expected = IndexOutOfBoundsException.class) - public void toJSONThrowsExceptionIfFromIndexInvalid() - { - this.matches.toJSON(-1, 3); - } - - @Test(expected = IndexOutOfBoundsException.class) - public void toJSONThrowsExceptionIfFromIndexIsGreaterThanDataSize() - { - this.matches.toJSON(300, 10); - } - - @Test - public void toJSONGetsCorrectDataForProvidedIndices() - { - final JSONObject result = this.matches.toJSON(1, 3); - final JSONObject expected = new JSONObject() - .put(QUERY_LABEL, new JSONObject() - .put(ID_LABEL, REFERENCE)) - .put(TOTAL_LABEL, 5) - .put(REQUEST, this.response.getRequestJSON()) - .put(RESPONSE, this.response.getResponseJSON()) - .put(RETURNED_LABEL, 3) - .put(OFFSET_LABEL, 2) - .put(RESULTS_LABEL, new JSONArray() - .put(new JSONObject() - .put(ID_LABEL, PATIENT_2)) - .put(new JSONObject() - .put(ID_LABEL, PATIENT_3)) - .put(new JSONObject() - .put(ID_LABEL, PATIENT_4))); - Assert.assertTrue(expected.similar(result)); - } - - @Test - public void toJSONGetsAllDataIfNoIndicesProvided() - { - final JSONObject result = this.matches.toJSON(); - final JSONObject expected = new JSONObject() - .put(QUERY_LABEL, new JSONObject() - .put(ID_LABEL, REFERENCE)) - .put(TOTAL_LABEL, 5) - .put(REQUEST, this.response.getRequestJSON()) - .put(RESPONSE, this.response.getResponseJSON()) - .put(RETURNED_LABEL, 5) - .put(OFFSET_LABEL, 1) - .put(RESULTS_LABEL, new JSONArray() - .put(new JSONObject() - .put(ID_LABEL, PATIENT_1)) - .put(new JSONObject() - .put(ID_LABEL, PATIENT_2)) - .put(new JSONObject() - .put(ID_LABEL, PATIENT_3)) - .put(new JSONObject() - .put(ID_LABEL, PATIENT_4)) - .put(new JSONObject() - .put(ID_LABEL, PATIENT_5))); - Assert.assertTrue(expected.similar(result)); - } - - @Test - public void equalsReturnsTrueForTwoDifferentObjectsWithSameData() - { - final MatchedPatientClusterView v2 = new RemoteMatchedPatientClusterView(this.reference, this.response, - this.matchList, null); - Assert.assertTrue(v2.equals(this.matches)); - } - - @Test - public void equalsReturnsTrueForTwoIdenticalObjects() - { - Assert.assertTrue(this.matches.equals(this.matches)); - } - - @Test - public void equalsReturnsFalseForTwoDifferentObjectsWithDifferentResponses() - { - final OutgoingMatchRequest response2 = mock(OutgoingMatchRequest.class); - final MatchedPatientClusterView v2 = new RemoteMatchedPatientClusterView(this.reference, response2, - this.matchList, null); - Assert.assertFalse(v2.equals(this.matches)); - } - - @Test - public void equalsReturnsFalseForTwoDifferentObjectsWithDifferentReference() - { - final MatchedPatientClusterView v2 = new RemoteMatchedPatientClusterView(mock(Patient.class), this.response, - this.matchList, null); - Assert.assertFalse(v2.equals(this.matches)); - } - - @Test - public void equalsReturnsFalseForTwoDifferentObjectsWithDifferentMatchList() - { - final List m2 = Arrays.asList(this.patient1, this.patient2, this.patient3); - final MatchedPatientClusterView v2 = new RemoteMatchedPatientClusterView(this.reference, this.response, m2, null); - Assert.assertFalse(v2.equals(this.matches)); - } - - @Test - public void hashCodeIsTheSameForTwoObjectsWithSameData() - { - final MatchedPatientClusterView v2 = new RemoteMatchedPatientClusterView(this.reference, this.response, - this.matchList, null); - Assert.assertEquals(v2.hashCode(), this.matches.hashCode()); - } -}