Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RHDM-919: Improved error messages #238

Merged
merged 8 commits into from
May 28, 2019
Expand Up @@ -20,34 +20,42 @@

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.core.MediaType;

import com.github.nmorel.gwtjackson.rest.api.RestCallback;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Response;
import org.jboss.errai.ui.client.local.spi.TranslationService;
import org.optaweb.employeerostering.gwtui.client.exception.RESTException;
import org.optaweb.employeerostering.gwtui.client.gwtjackson.ServerSideExceptionDeserializer;
import org.optaweb.employeerostering.shared.exception.ServerSideException;
import org.optaweb.employeerostering.gwtui.client.notification.NotificationFactory;
import org.optaweb.employeerostering.shared.exception.ServerSideExceptionInfo;

@Singleton
public class FailureShownRestCallbackFactory {

@Inject
private TranslationService translationService;

@Inject
private NotificationFactory notificationFactory;

@Inject
private ServerSideExceptionDeserializer serverSideExceptionDeserializer;

public abstract class FailureShownRestCallback<T> extends RestCallback<T> {

private Consumer<Response> onError = response -> {
ServerSideException exception = serverSideExceptionDeserializer.deserializeFromJsonString(response.getText());
GWT.getUncaughtExceptionHandler().onUncaughtException(new RESTException(exception, translationService));
if (response.getHeader("Content-Type").equals(MediaType.APPLICATION_JSON)) {
ServerSideExceptionInfo exception = serverSideExceptionDeserializer.deserializeFromJsonString(response.getText());
GWT.getUncaughtExceptionHandler().onUncaughtException(new RESTException(exception, translationService));
} else {
notificationFactory.showErrorMessage(response.getText());
}
};

private Consumer<Throwable> onFailure = throwable -> {
GWT.getUncaughtExceptionHandler().onUncaughtException(throwable);
};
private Consumer<Throwable> onFailure = throwable ->
GWT.getUncaughtExceptionHandler().onUncaughtException(throwable);

@Override
public void onError(final Response response) {
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,27 +17,31 @@
package org.optaweb.employeerostering.gwtui.client.exception;

import org.jboss.errai.ui.client.local.spi.TranslationService;
import org.optaweb.employeerostering.shared.exception.ServerSideException;
import org.optaweb.employeerostering.shared.exception.ServerSideExceptionInfo;

public class RESTException extends Exception {

private ServerSideException serverSideException;
private TranslationService translationService;
private final transient ServerSideExceptionInfo serverSideException;
private final transient TranslationService translationService;

public RESTException(ServerSideException serverSideException, TranslationService translationService) {
public RESTException(ServerSideExceptionInfo serverSideException, TranslationService translationService) {
super(getExceptionFrom(serverSideException));
this.serverSideException = serverSideException;
this.translationService = translationService;
}

private static Exception getExceptionFrom(ServerSideException serverSideException) {
private static Exception getExceptionFrom(ServerSideExceptionInfo serverSideException) {
Exception out;
if (serverSideException.getExceptionCause() != null) {
out = new ServerException(getExceptionFrom(serverSideException.getExceptionCause()), serverSideException.getExceptionClass() + ": " + serverSideException.getExceptionMessage());
} else {
out = new ServerException(serverSideException.getExceptionClass() + ": " + serverSideException.getExceptionMessage());
}
out.setStackTrace(extractStackTrace(serverSideException));
return out;
}

private static StackTraceElement[] extractStackTrace(ServerSideExceptionInfo serverSideException) {
Christopher-Chianelli marked this conversation as resolved.
Show resolved Hide resolved
StackTraceElement[] stackTrace = new StackTraceElement[serverSideException.getStackTrace().size()];
Christopher-Chianelli marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < stackTrace.length; i++) {
String stackTraceLine = serverSideException.getStackTrace().get(i);
Expand All @@ -55,8 +59,7 @@ private static Exception getExceptionFrom(ServerSideException serverSideExceptio
stackTrace[i] = new StackTraceElement(className, methodName, "Unknown Source", 0);
}
}
out.setStackTrace(stackTrace);
return out;
return stackTrace;
}

@Override
Expand All @@ -67,7 +70,7 @@ public String getMessage() {

private static class ServerException extends Exception {

private String message;
private final String message;

public ServerException(Throwable cause, String message) {
super(cause);
Expand Down
Expand Up @@ -28,14 +28,15 @@
import com.github.nmorel.gwtjackson.client.stream.JsonToken;
import com.github.nmorel.gwtjackson.client.stream.impl.DefaultJsonReader;
import com.github.nmorel.gwtjackson.client.stream.impl.StringReader;
import org.optaweb.employeerostering.shared.exception.ServerSideException;
import org.optaweb.employeerostering.shared.exception.ServerSideExceptionInfo;
import org.optaweb.employeerostering.shared.exception.ServerSideExceptionInfo.ServerSideExceptionInfoFields;

@Singleton
public class ServerSideExceptionDeserializer {
Christopher-Chianelli marked this conversation as resolved.
Show resolved Hide resolved

// Work around for https://github.com/nmorel/gwt-jackson/issues/79
// (Cannot use the gwt-jackson ObjectMapper due to a compilation error)
public ServerSideException deserializeFromJsonString(String json) {
public ServerSideExceptionInfo deserializeFromJsonString(String json) {
JsonReader reader = new DefaultJsonReader(new StringReader(json));
try {
return deserialize(reader, json);
Expand All @@ -44,43 +45,41 @@ public ServerSideException deserializeFromJsonString(String json) {
}
}

public ServerSideException deserialize(JsonReader reader, String json) {
Set<String> fields = new HashSet<>(Arrays.asList("i18nKey", "messageParameters", "exceptionClass", "stackTrace", "exceptionMessage", "exceptionCause"));
public ServerSideExceptionInfo deserialize(JsonReader reader, String json) {
Set<ServerSideExceptionInfoFields> fields = new HashSet<>(Arrays.asList(ServerSideExceptionInfoFields.values()));

String i18nKey = null;
String exceptionMessage = null;
List<String> messageParameters = null;
String exceptionClass = null;
List<String> stackTrace = null;
ServerSideException exceptionCause = null;
ServerSideExceptionInfo exceptionCause = null;

reader.beginObject();
while (reader.hasNext()) {
String fieldName = reader.nextName();
if (!fields.contains(fieldName)) {
throw new IllegalArgumentException("Invalid Json: (" + json + ").");
}
fields.remove(fieldName);
ServerSideExceptionInfoFields field = ServerSideExceptionInfoFields.getFieldForName(fieldName);
fields.remove(field);

switch (fieldName) {
case "i18nKey":
switch (field) {
case I18N_KEY:
i18nKey = reader.nextString();
break;
case "exceptionMessage":
case EXCEPTION_MESSAGE:
exceptionMessage = reader.nextString();
break;
case "messageParameters":
case MESSAGE_PARAMETERS:
reader.beginArray();
messageParameters = new ArrayList<>();
while (reader.hasNext()) {
messageParameters.add(reader.nextString());
}
reader.endArray();
break;
case "exceptionClass":
case EXCEPTION_CLASS:
exceptionClass = reader.nextString();
break;
case "stackTrace":
case STACK_TRACE:
reader.beginArray();
stackTrace = new ArrayList<>();
while (reader.hasNext()) {
Expand All @@ -89,7 +88,7 @@ public ServerSideException deserialize(JsonReader reader, String json) {
reader.endArray();
break;

case "exceptionCause":
case EXCEPTION_CAUSE:
if (reader.peek() != JsonToken.NULL) {
exceptionCause = deserialize(reader, json);
} else {
Expand All @@ -98,13 +97,17 @@ public ServerSideException deserialize(JsonReader reader, String json) {
}
break;
default:
throw new IllegalArgumentException("Invalid Json: (" + json + ").");
raiseError(json);
}
}
reader.endObject();
if (!fields.isEmpty()) {
throw new IllegalArgumentException("Invalid Json: (" + json + ").");
raiseError(json);
}
return new ServerSideException(i18nKey, exceptionMessage, messageParameters, exceptionClass, stackTrace, exceptionCause);
return new ServerSideExceptionInfo(i18nKey, exceptionMessage, messageParameters, exceptionClass, stackTrace, exceptionCause);
}

private static void raiseError(String json) {
throw new IllegalArgumentException("Invalid Json: (" + json + ").");
}
}
Expand Up @@ -120,7 +120,7 @@ public void refresh(final @ForEvent("click") MouseEvent e) {
private void initTable() {
searchBar.setListToFilter(Collections.emptyList());
pager.setPresenter(table);
searchBar.setElementToStringMapping((tenant) -> tenant.getName());
searchBar.setElementToStringMapping(Tenant::getName);
searchBar.addFilterListener(pager);
}

Expand All @@ -137,7 +137,7 @@ private Promise<Void> refresh() {
@EventHandler("reset-application-button")
private void resetApplication(@ForEvent("click") MouseEvent e) {
loadingSpinner.showFor("reset-application");
AdminRestServiceBuilder.resetApplication(null, restCallbackFactory.onSuccess((success) -> {
AdminRestServiceBuilder.resetApplication(null, restCallbackFactory.onSuccess(success -> {
loadingSpinner.hideFor("reset-application");
notificationFactory.showInfoMessage(I18nKeys.Notifications_resetApplicationSuccessful);
}));
Expand Down
Expand Up @@ -113,9 +113,9 @@ private void onClick(@ForEvent("click") MouseEvent e) {
private void onDeleteClick(@ForEvent("click") MouseEvent e) {
e.stopPropagation();
EmployeeRestServiceBuilder.removeEmployeeAvailability(employeeAvailabilityView.getTenantId(), employeeAvailabilityView.getId(),
restCallbackFactory.onSuccess(success -> {
getLane().removeGridObject(this);
}));
restCallbackFactory.onSuccess(success ->
getLane().removeGridObject(this)
));
}

@EventHandler("timeslot-unavailable")
Expand All @@ -139,9 +139,8 @@ private void onTimeslotDesiredButtonClick(@ForEvent("click") MouseEvent e) {
private void setEmployeeAvailabilityState(EmployeeAvailabilityState state) {
employeeAvailabilityView.setState(state);
EmployeeRestServiceBuilder.updateEmployeeAvailability(employeeAvailabilityView.getTenantId(), employeeAvailabilityView,
restCallbackFactory.onSuccess(eav -> {
withEmployeeAvailabilityView(eav);
}));
restCallbackFactory.onSuccess(
this::withEmployeeAvailabilityView));
}

@Override
Expand Down Expand Up @@ -200,8 +199,6 @@ public Employee getEmployee() {
@Override
public void save() {
EmployeeRestServiceBuilder.updateEmployeeAvailability(employeeAvailabilityView.getTenantId(), employeeAvailabilityView,
restCallbackFactory.onSuccess(eav -> {
withEmployeeAvailabilityView(eav);
}));
restCallbackFactory.onSuccess(this::withEmployeeAvailabilityView));
}
}
Expand Up @@ -100,16 +100,16 @@ public class AvailabilityRosterPageViewportBuilder {
@PostConstruct
private void init() {
pagination = Pagination.of(0, 10);
eventManager.subscribeToEventForever(SOLVE_START, (m) -> this.onSolveStart());
eventManager.subscribeToEventForever(SOLVE_END, (m) -> this.onSolveEnd());
eventManager.subscribeToEventForever(AVAILABILITY_ROSTER_PAGINATION, (pagination) -> {
eventManager.subscribeToEventForever(SOLVE_START, m -> this.onSolveStart());
eventManager.subscribeToEventForever(SOLVE_END, m -> this.onSolveEnd());
eventManager.subscribeToEventForever(AVAILABILITY_ROSTER_PAGINATION, pagination -> {
this.pagination = pagination;
buildAvailabilityRosterViewport(viewport);
});
eventManager.subscribeToEventForever(AVAILABILITY_ROSTER_INVALIDATE, (nil) -> {
eventManager.subscribeToEventForever(AVAILABILITY_ROSTER_INVALIDATE, nil -> {
buildAvailabilityRosterViewport(viewport);
});
eventManager.subscribeToEventForever(DATA_INVALIDATION, (dataInvalidated) -> {
eventManager.subscribeToEventForever(DATA_INVALIDATION, dataInvalidated -> {
if (dataInvalidated.equals(Employee.class) || dataInvalidated.equals(EmployeeAvailability.class)) {
buildAvailabilityRosterViewport(viewport);
}
Expand All @@ -119,7 +119,7 @@ private void init() {
buildAvailabilityRosterViewport(viewport);
});
RosterRestServiceBuilder.getRosterState(tenantStore.getCurrentTenantId(),
restCallbackFactory.onSuccess((rs) -> {
restCallbackFactory.onSuccess(rs -> {
LocalDate startDate = dateTimeUtils.getFirstDateOfWeek(rs.getFirstDraftDate());
LocalDate endDate = dateTimeUtils.getLastDateOfWeek(rs.getFirstDraftDate()).plusDays(1);
eventManager.fireEvent(AVAILABILITY_ROSTER_DATE_RANGE, new LocalDateRange(startDate, endDate));
Expand Down Expand Up @@ -264,8 +264,6 @@ public Promise<AvailabilityRosterView> getAvailabilityRosterView() {
.getStartDate()
.toString(),
localDateRange.getEndDate().toString(),
restCallbackFactory.onSuccess((s) -> {
res.onInvoke(s);
})));
restCallbackFactory.onSuccess(res::onInvoke)));
}
}
Expand Up @@ -92,8 +92,8 @@ public void addEmployeeAvailability(@ForEvent("click") MouseEvent e) {
}

private void updateRowCount() {
EmployeeRestServiceBuilder.getEmployeeList(tenantStore.getCurrentTenantId(), restCallbackFactory.onSuccess(employeeList -> {
setRowCount(employeeList.size());
}));
EmployeeRestServiceBuilder.getEmployeeList(tenantStore.getCurrentTenantId(), restCallbackFactory.onSuccess(employeeList ->
setRowCount(employeeList.size())
));
}
}
Expand Up @@ -99,8 +99,6 @@ protected void init(Lane<LocalDateTime, AvailabilityRosterMetadata> lane) {
@Override
public void save() {
ShiftRestServiceBuilder.updateShift(shiftView.getTenantId(), shiftView,
restCallbackFactory.onSuccess(sv -> {
withShiftView(sv);
}));
restCallbackFactory.onSuccess(this::withShiftView));
}
}
Expand Up @@ -84,8 +84,8 @@ public void onEditContractButtonClick(@ForEvent("click") MouseEvent e) {

@EventHandler("delete")
public void onDeleteContractButtonClick(@ForEvent("click") MouseEvent e) {
ContractRestServiceBuilder.removeContract(tenantStore.getCurrentTenantId(), contract.getId(), restCallbackFactory.onSuccess(v -> {
eventManager.fireEvent(EventManager.Event.DATA_INVALIDATION, Contract.class);
}));
ContractRestServiceBuilder.removeContract(tenantStore.getCurrentTenantId(), contract.getId(), restCallbackFactory.onSuccess(v ->
eventManager.fireEvent(EventManager.Event.DATA_INVALIDATION, Contract.class)
));
}
}