dev.fitko.fitconnect.core.cases.CaseApiService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client Show documentation
Show all versions of client Show documentation
Library that provides client access to the FIT-Connect api-endpoints for sending, subscribing and
routing
package dev.fitko.fitconnect.core.cases;
import com.nimbusds.jwt.SignedJWT;
import dev.fitko.fitconnect.api.domain.model.cases.Case;
import dev.fitko.fitconnect.api.domain.model.cases.Cases;
import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventLog;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.Status;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.reply.Reply;
import dev.fitko.fitconnect.api.domain.model.reply.SentReply;
import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.validation.ValidationContext;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.internal.AuthenticationTagsEmptyException;
import dev.fitko.fitconnect.api.exceptions.internal.EventLogException;
import dev.fitko.fitconnect.api.exceptions.internal.RestApiException;
import dev.fitko.fitconnect.api.exceptions.internal.SubmitEventNotFoundException;
import dev.fitko.fitconnect.api.services.auth.OAuthService;
import dev.fitko.fitconnect.api.services.events.CaseService;
import dev.fitko.fitconnect.api.services.events.EventLogVerificationService;
import dev.fitko.fitconnect.api.services.http.HttpClient;
import dev.fitko.fitconnect.core.http.HttpHeaders;
import dev.fitko.fitconnect.core.http.MimeTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.eventSubjectEqualsId;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.eventToLogEntry;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.getAuthTags;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.getFilteredJwtsFromEventLog;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.getJWTSFromEvents;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.mapEventLogToEntries;
public class CaseApiService implements CaseService {
public static final String EVENTS_PATH = "/v1/cases/%s/events";
public static final String CASES_PATH = "/v1/cases";
public static final String CASE_PATH = "/v1/cases/%s";
private static final Logger LOGGER = LoggerFactory.getLogger(CaseApiService.class);
private final OAuthService authService;
private final HttpClient httpClient;
private final EventLogVerificationService eventLogVerifier;
private final String baseUrl;
public CaseApiService(final OAuthService authService,
final HttpClient httpClient,
final EventLogVerificationService eventLogVerifier,
final String baseUrl) {
this.authService = authService;
this.httpClient = httpClient;
this.eventLogVerifier = eventLogVerifier;
this.baseUrl = baseUrl;
}
@Override
public List getEventLog(final UUID caseId, final UUID destinationId) throws RestApiException, EventLogException {
final EventLog eventLog = loadEventLog(caseId);
final List events = getJWTSFromEvents(eventLog.getEventLogs());
final ValidationContext ctx = ValidationContext.withoutAuthTagValidation(destinationId, caseId);
checkForValidationErrors(eventLogVerifier.validateEventLogs(ctx, events));
return mapEventLogToEntries(events);
}
@Override
public AuthenticationTags getAuthenticationTags(final Submission submission) throws RestApiException, EventLogException {
final SignedJWT submitEvent = getEvent(submission.getCaseId(), Event.SUBMIT_SUBMISSION, submission.getSubmissionId());
final AuthenticationTags authenticationTags = getAuthTagsFromEvent(submitEvent);
final var ctx = ValidationContext.withAuthTagValidation(submission.getDestinationId(), submission.getCaseId(), authenticationTags);
checkForValidationErrors(eventLogVerifier.validateEventLogs(ctx, List.of(submitEvent)));
return authenticationTags;
}
@Override
public AuthenticationTags getAuthenticationTags(final Reply reply) throws RestApiException, EventLogException {
final SignedJWT submitEvent = getEvent(reply.getCaseId(), Event.SUBMIT_REPLY, reply.getReplyId());
final AuthenticationTags authenticationTags = getAuthTagsFromEvent(submitEvent);
final var ctx = ValidationContext.withAuthTagValidation(null, reply.getCaseId(), authenticationTags);
checkForValidationErrors(eventLogVerifier.validateEventLogs(ctx, List.of(submitEvent)));
return authenticationTags;
}
@Override
public void sendEvent(final UUID caseId, final String signedAndSerializedSET) {
final String url = String.format(baseUrl + EVENTS_PATH, caseId);
try {
httpClient.post(url, getHttpHeaders(MimeTypes.APPLICATION_JOSE), signedAndSerializedSET, Void.class);
} catch (final RestApiException e) {
throw new RestApiException("Sending event failed", e);
}
}
@Override
public Status getStatus(final SentSubmission sentSubmission) {
final SignedJWT latestEvent = getLogFilteredById(sentSubmission.getCaseId(), sentSubmission.getSubmissionId());
final ValidationContext contextWithoutAuthTagValidation = ValidationContext.withoutAuthTagValidation(sentSubmission.getDestinationId(), sentSubmission.getCaseId());
return getVerifiedStatus(contextWithoutAuthTagValidation, latestEvent);
}
@Override
public Status getStatus(final SentReply reply) {
final SignedJWT latestEvent = getLogFilteredById(reply.getCaseId(), reply.getReplyId());
//FIXME check if destination id check is needed here for replies.
final ValidationContext contextWithoutAuthTagValidation = ValidationContext.withoutAuthTagValidation(reply.getCaseId());
return getVerifiedStatus(contextWithoutAuthTagValidation, latestEvent);
}
@Override
public Status getSubmissionSubmitState(final UUID caseId, final UUID submissionId) {
final SignedJWT submitEvent = getEvent(caseId, Event.SUBMIT_SUBMISSION, submissionId);
final var ctx = ValidationContext.withoutAuthTagValidation(caseId);
checkForValidationErrors(eventLogVerifier.validateEventLogs(ctx, List.of(submitEvent)));
final EventLogEntry eventLogEntry = eventToLogEntry(submitEvent);
return Status.fromEventLogEntry(eventLogEntry);
}
@Override
public Cases listCases(final int limit, final int offset) {
return loadCases(limit, offset);
}
@Override
public Case getCase(final UUID caseId) {
final String url = String.format(baseUrl + CASE_PATH, caseId);
try {
return httpClient.get(url, getHttpHeaders(MimeTypes.APPLICATION_JSON), Case.class).getBody();
} catch (final RestApiException e) {
throw new RestApiException("Case query failed", e);
}
}
private SignedJWT getEvent(final UUID caseId, final Event event, final UUID subjectFilter) {
final EventLog eventLog = loadEventLog(caseId);
final List submitEvents = getFilteredJwtsFromEventLog(subjectFilter, event, eventLog);
if (submitEvents.size() != 1) {
throw new SubmitEventNotFoundException("Event log does not contain exactly one submit event");
}
return submitEvents.stream().findFirst().get();
}
private AuthenticationTags getAuthTagsFromEvent(final SignedJWT submitEvent) {
final AuthenticationTags authenticationTags = getAuthTags(submitEvent);
if (authenticationTags == null || authenticationTags.getMetadata() == null && authenticationTags.getData() == null) {
throw new AuthenticationTagsEmptyException("Authentication tags are empty");
}
return authenticationTags;
}
private EventLog loadEventLog(final UUID caseId) {
final String url = String.format(baseUrl + EVENTS_PATH, caseId);
try {
return httpClient.get(url, getHttpHeaders(MimeTypes.APPLICATION_JSON), EventLog.class).getBody();
} catch (final RestApiException e) {
throw new RestApiException("EventLog query failed", e);
}
}
private Cases loadCases(final int limit, final int offset) {
final String urlWithQueryParams = baseUrl + CASES_PATH + "?limit=" + limit + "&offset=" + offset;
try {
return httpClient.get(urlWithQueryParams, getHttpHeaders(MimeTypes.APPLICATION_JSON), Cases.class).getBody();
} catch (final RestApiException e) {
throw new RestApiException("Cases query failed", e);
}
}
private Map getHttpHeaders(final String mediaType) {
return new HashMap<>(Map.of(
HttpHeaders.AUTHORIZATION, "Bearer " + authService.getCurrentToken().getAccessToken(),
HttpHeaders.CONTENT_TYPE, mediaType,
HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.toString()));
}
private Status getVerifiedStatus(final ValidationContext validationContext, final SignedJWT latestEvent) {
if (latestEvent == null) {
LOGGER.info("No events found");
return new Status();
}
checkForValidationErrors(eventLogVerifier.validateEventLogs(validationContext, List.of(latestEvent)));
final EventLogEntry eventLogEntry = eventToLogEntry(latestEvent);
return Status.fromEventLogEntry(eventLogEntry);
}
private SignedJWT getLogFilteredById(final UUID caseId, final UUID id) {
final EventLog eventLog = loadEventLog(caseId);
return getJWTSFromEvents(eventLog.getEventLogs()).stream()
.filter(eventSubjectEqualsId(id))
.reduce((first, second) -> second)
.orElse(null);
}
private void checkForValidationErrors(final List validationResults) {
if (!validationResults.isEmpty()) {
throw new EventLogException(joinMessages(validationResults));
}
}
private String joinMessages(final List validationResults) {
return validationResults.stream()
.map(ValidationResult::getError)
.map(Exception::getMessage)
.collect(Collectors.joining("\n"));
}
}