Better error handling and logging
This commit is contained in:
parent
329293cf9a
commit
078ab15a86
9 changed files with 80 additions and 21 deletions
|
@ -174,7 +174,7 @@ public final class CircuitBreaker<T> {
|
|||
this.state = State.HALF_OPEN;
|
||||
this.failingCount.set(0);
|
||||
return Result.ofError(new RuntimeException(
|
||||
"Set CircuitBeaker to half-open state. Cause: ",
|
||||
"Set CircuitBeaker to half-open state. Cause: " + result.getError().getMessage(),
|
||||
result.getError()));
|
||||
} else {
|
||||
// try again
|
||||
|
@ -204,7 +204,7 @@ public final class CircuitBreaker<T> {
|
|||
|
||||
this.state = State.OPEN;
|
||||
return Result.ofError(new RuntimeException(
|
||||
"Set CircuitBeaker to open state. Cause: ",
|
||||
"Set CircuitBeaker to open state. Cause: " + result.getError().getMessage(),
|
||||
result.getError()));
|
||||
} else {
|
||||
// on success go to CLOSED state
|
||||
|
|
|
@ -71,4 +71,13 @@ public interface RestService {
|
|||
EntityType entityType,
|
||||
CallType callType);
|
||||
|
||||
/** Use this to inject the current SEB Server API access RestTemplate to a long living
|
||||
* RestCallBuilder. This is usually used to recover from a disposed API access RestTemplate.
|
||||
*
|
||||
* @param <T> The generic type of RestCallBuilder
|
||||
* @param builder the RestCallBuilder to inject the current RestTemplate into. */
|
||||
default <T> void injectCurrentRestTemplate(final RestCall<T>.RestCallBuilder builder) {
|
||||
builder.withRestTemplate(getWebserviceAPIRestTemplate());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.auth;
|
||||
|
||||
public class DisposedOAuth2RestTemplateException extends IllegalStateException {
|
||||
|
||||
private static final long serialVersionUID = -8439656564917103027L;
|
||||
|
||||
public DisposedOAuth2RestTemplateException(final String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
}
|
|
@ -132,7 +132,7 @@ public class OAuth2AuthorizationContextHolder implements AuthorizationContextHol
|
|||
if (this.enabled) {
|
||||
return super.doExecute(url, method, requestCallback, responseExtractor);
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
throw new DisposedOAuth2RestTemplateException(
|
||||
"Error: Forbidden execution call on disabled DisposableOAuth2RestTemplate");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
|
||||
|
@ -135,6 +136,7 @@ public class ClientConnectionDetails {
|
|||
.call()
|
||||
.get(error -> {
|
||||
log.error("Unexpected error while trying to get current client connection data: ", error);
|
||||
recoverFromDisposedRestTemplate(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
|
@ -234,4 +236,13 @@ public class ClientConnectionDetails {
|
|||
pageContext);
|
||||
}
|
||||
|
||||
public void recoverFromDisposedRestTemplate(final Exception error) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Try to recover from disposed OAuth2 rest template...");
|
||||
}
|
||||
if (error instanceof DisposedOAuth2RestTemplateException) {
|
||||
this.pageService.getRestService().injectCurrentRestTemplate(this.restCallBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,6 +62,7 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
|
@ -90,8 +91,7 @@ public final class ClientConnectionTable {
|
|||
private final static LocTextKey CONNECTION_STATUS_TOOLTIP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.connection.list.column.status" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final ResourceService resourceService;
|
||||
private final PageService pageService;
|
||||
private final AsyncRunner asyncRunner;
|
||||
private final Exam exam;
|
||||
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
|
||||
|
@ -124,12 +124,14 @@ public final class ClientConnectionTable {
|
|||
final Collection<Indicator> indicators,
|
||||
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) {
|
||||
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.pageService = pageService;
|
||||
this.asyncRunner = asyncRunner;
|
||||
this.exam = exam;
|
||||
this.restCallBuilder = restCallBuilder;
|
||||
|
||||
final WidgetFactory widgetFactory = pageService.getWidgetFactory();
|
||||
final ResourceService resourceService = pageService.getResourceService();
|
||||
|
||||
final Display display = tableRoot.getDisplay();
|
||||
this.colorData = new ColorData(display);
|
||||
|
||||
|
@ -143,11 +145,11 @@ public final class ClientConnectionTable {
|
|||
NUMBER_OF_NONE_INDICATOR_COLUMNS);
|
||||
|
||||
this.localizedClientConnectionStatusNameFunction =
|
||||
this.resourceService.localizedClientConnectionStatusNameFunction();
|
||||
resourceService.localizedClientConnectionStatusNameFunction();
|
||||
this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
|
||||
loadStatusFilter();
|
||||
|
||||
this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL);
|
||||
this.table = widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL);
|
||||
final GridLayout gridLayout = new GridLayout(3 + indicators.size(), false);
|
||||
gridLayout.horizontalSpacing = 100;
|
||||
gridLayout.marginWidth = 100;
|
||||
|
@ -161,20 +163,20 @@ public final class ClientConnectionTable {
|
|||
this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
|
||||
this.table.addListener(SWT.MouseUp, this::notifyTableInfoClick);
|
||||
|
||||
this.widgetFactory.tableColumnLocalized(
|
||||
widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
CONNECTION_ID_TEXT_KEY,
|
||||
CONNECTION_ID_TOOLTIP_TEXT_KEY);
|
||||
this.widgetFactory.tableColumnLocalized(
|
||||
widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
CONNECTION_ADDRESS_TEXT_KEY,
|
||||
CONNECTION_ADDRESS_TOOLTIP_TEXT_KEY);
|
||||
this.widgetFactory.tableColumnLocalized(
|
||||
widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
CONNECTION_STATUS_TEXT_KEY,
|
||||
CONNECTION_STATUS_TOOLTIP_TEXT_KEY);
|
||||
for (final Indicator indDef : indicators) {
|
||||
final TableColumn tableColumn = this.widgetFactory.tableColumnLocalized(
|
||||
final TableColumn tableColumn = widgetFactory.tableColumnLocalized(
|
||||
this.table,
|
||||
new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.name),
|
||||
new LocTextKey(INDICATOR_NAME_TEXT_KEY_PREFIX + indDef.type.name));
|
||||
|
@ -187,7 +189,7 @@ public final class ClientConnectionTable {
|
|||
}
|
||||
|
||||
public WidgetFactory getWidgetFactory() {
|
||||
return this.widgetFactory;
|
||||
return this.pageService.getWidgetFactory();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
|
@ -325,7 +327,11 @@ public final class ClientConnectionTable {
|
|||
this.restCallBuilder
|
||||
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
|
||||
.call()
|
||||
.getOrThrow()
|
||||
.get(error -> {
|
||||
log.error("Unexpected error while trying to get client connection table data: ", error);
|
||||
recoverFromDisposedRestTemplate(error);
|
||||
return Collections.emptyList();
|
||||
})
|
||||
.forEach(data -> {
|
||||
final UpdatableTableItem tableItem = this.tableMapping.computeIfAbsent(
|
||||
data.getConnectionId(),
|
||||
|
@ -425,7 +431,7 @@ public final class ClientConnectionTable {
|
|||
|
||||
private void saveStatusFilter() {
|
||||
try {
|
||||
this.resourceService
|
||||
this.pageService
|
||||
.getCurrentUser()
|
||||
.putAttribute(
|
||||
USER_SESSION_STATUS_FILTER_ATTRIBUTE,
|
||||
|
@ -440,7 +446,7 @@ public final class ClientConnectionTable {
|
|||
|
||||
private void loadStatusFilter() {
|
||||
try {
|
||||
final String attribute = this.resourceService
|
||||
final String attribute = this.pageService
|
||||
.getCurrentUser()
|
||||
.getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
|
||||
this.statusFilter.clear();
|
||||
|
@ -707,4 +713,13 @@ public final class ClientConnectionTable {
|
|||
|
||||
}
|
||||
|
||||
public void recoverFromDisposedRestTemplate(final Exception error) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Try to recover from disposed OAuth2 rest template...");
|
||||
}
|
||||
if (error instanceof DisposedOAuth2RestTemplateException) {
|
||||
this.pageService.getRestService().injectCurrentRestTemplate(this.restCallBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -941,7 +941,7 @@ public class ExamDAOImpl implements ExamDAO {
|
|||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to try to recover from Moodle quiz restore: ", e.getMessage());
|
||||
log.warn("Failed to try to recover from Moodle quiz restore: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -448,14 +448,14 @@ public class MoodleCourseAccess extends CourseAccess {
|
|||
|
||||
if (courses == null) {
|
||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||
Collections.emptyList();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
logMoodleWarnings(courses.warnings);
|
||||
|
||||
if (courses.courses == null || courses.courses.isEmpty()) {
|
||||
log.error("No courses found for ids: {} on LMS: {}", ids, this.lmsSetup.name);
|
||||
Collections.emptyList();
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return courses.courses;
|
||||
|
|
|
@ -324,9 +324,14 @@ class MoodleRestTemplateFactory {
|
|||
}
|
||||
|
||||
final String body = response.getBody();
|
||||
|
||||
// NOTE: for some unknown reason, Moodles API error responses come with a 200 OK response HTTP Status
|
||||
// So this is a special Moodle specific error handling here...
|
||||
if (body.startsWith("{exception")) {
|
||||
if (body.startsWith("{exception") || body.contains("\"exception\":")) {
|
||||
// Reset access token to get new on next call (fix access if token is expired)
|
||||
// TODO find a way to verify token invalidity response from Moodle.
|
||||
// Unfortunately there is not a lot of Moodle documentation for the API error handling around.
|
||||
this.accessToken = null;
|
||||
throw new RuntimeException(
|
||||
"Failed to call Moodle webservice API function: " + functionName + " lms setup: " +
|
||||
MoodleRestTemplateFactory.this.lmsSetup + " response: " + body);
|
||||
|
|
Loading…
Add table
Reference in a new issue