/*
* Copyright (C) 2012 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.webapp.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.i18n.SmartGwtMessages;
import com.smartgwt.client.rpc.HandleErrorCallback;
import com.smartgwt.client.rpc.HandleTransportErrorCallback;
import com.smartgwt.client.rpc.RPCManager;
import com.smartgwt.client.rpc.RPCResponse;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Button;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.Dialog;
import com.smartgwt.client.widgets.layout.VLayout;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Global Smart GWT RPC error handler.
*
* <p>Replaces default {@link HandleTransportErrorCallback} and {@link HandleErrorCallback}
* to provide more detailed notifications of RPC errors and to make available
* {@link #getTransportError() HTTP response text} in case of HTTP error.
*
* <p>Each {@link DataSource} can override global handler with custom one with {@link DataSource#addHandleErrorHandler }.
* Then it is still necessary to cancel the {@link com.smartgwt.client.data.events.ErrorEvent event}.
*
* @author Jan Pokorsky
*/
public final class ErrorHandler {
private static final Logger LOG = Logger.getLogger(ErrorHandler.class.getName());
private TransportError transportError;
private final ClientMessages i18n;
public ErrorHandler() {
this(GWT.<ClientMessages>create(ClientMessages.class));
}
public ErrorHandler(ClientMessages i18n) {
this.i18n = i18n;
}
/**
* Gets last transport error or {@code null}.
*/
public TransportError getTransportError() {
return transportError;
}
/**
* Registers custom error handler.
*/
void initTransportErrorHandler() {
RPCManager.setHandleTransportErrorCallback(new HandleTransportErrorCallback() {
@Override
public void handleTransportError(int transactionNum, int status, int httpResponseCode, String httpResponseText) {
transportError = new TransportError(transactionNum, status, httpResponseCode, httpResponseText);
}
});
RPCManager.setHandleErrorCallback(new HandleErrorCallback() {
@Override
public void handleError(DSResponse response, DSRequest request) {
TransportError te = transportError;
ErrorHandler.this.handleError(te, response, request);
}
});
}
/**
* Gets simple message from error response for user.
*/
private String getClientMessage(TransportError te, DSResponse response, DSRequest request) {
String contentType = String.valueOf(response.getHttpHeaders().get("Content-Type"));
String result;
if (response.getStatus() == RPCResponse.STATUS_FAILURE) {
return response.getDataAsString();
}
if (contentType.contains("text/plain")) {
result = te == null ? null: te.getHttpResponseText();
} else {
result = i18n.ErrorHandler_UnexpectedError_Msg();
}
if (result == null || result.isEmpty() || result.length() > 1000) {
// message from original error handler; contains URL
return response.getDataAsString();
} else {
return result;
}
}
private void handleError(TransportError te, DSResponse response, DSRequest request) {
String requestDump = ClientUtils.dump(request.getJsObj());
// message from original error handler; contains URL
Object smartGwtMsg = response.getDataAsString();
if (te == null || response.getTransactionNum() != te.getTransactionNum()) {
String debugInfo = ClientUtils.format("Invalid transaction numbers %s != %s"
+"\n%s\nStatus: %s\nQuery: %s",
response.getTransactionNum(),
te == null ? null : te.getTransactionNum(),
smartGwtMsg,
response.getStatus(),
requestDump
);
SC.logWarn(debugInfo);
}
// boolean clientError = te.getHttpResponseCode() >= 400 && te.getHttpResponseCode() < 500;
String clientMsg = getClientMessage(te, response, request);
String debugInfo = ClientUtils.format("%s\nStatus: %s\nQuery: %s",
smartGwtMsg,
te == null ? response.getStatus() : te.getStatus(),
requestDump
);
String htmlDebugInfo = ClientUtils.format("%s<br/>Status: %s<br/>Query: %s",
smartGwtMsg,
te == null ? response.getStatus() : te.getStatus(),
SafeHtmlUtils.htmlEscape(requestDump)
);
warn(clientMsg, te == null ? response.getDataAsString(): te.getHttpResponseText(), htmlDebugInfo);
SC.logWarn(debugInfo);
}
/**
* The default notification about an error.
*/
public static void warn(DSResponse response, DSRequest request) {
new ErrorHandler().handleError(new TransportError(response.getTransactionNum(),
response.getStatus(), response.getHttpResponseCode(), response.getHttpResponseText()),
response, request);
}
/**
* The default notification about an error using a text message.
*/
public static void warn(String error) {
new ErrorHandler().warn(null, error, null);
}
/**
* Notifies user about error.
* @param msg simple client message
* @param detailMsg detail response message; can be HTML
* @param debugInfo request details, URL, ...
*/
private void warn(String msg, String detailMsg, String debugInfo) {
if (msg == null) {
msg = i18n.ErrorHandler_UnexpectedError_Msg();
}
SmartGwtMessages sgi18n = ClientUtils.createSmartGwtMessages();
boolean allowDetail = !msg.equals(detailMsg);
final Dialog d = new Dialog();
d.setTitle(sgi18n.dialog_WarnTitle());
d.setIsModal(true);
d.setAutoSize(Boolean.FALSE);
d.setMessage(msg);
d.setIcon("[SKIN]warn.png");
d.setCanDragResize(true);
d.setCanDragReposition(Boolean.TRUE);
d.setKeepInParentRect(Boolean.TRUE);
// d.setShowMaximizeButton(Boolean.TRUE);
d.setMinMemberSize(50);
Button details = new Button(i18n.ErrorHandler_ButtonDetalis_Title());
details.setVisible(allowDetail);
d.setButtons(Dialog.OK, details);
if (allowDetail) {
Canvas errorPane = new Canvas();
errorPane.setOverflow(Overflow.AUTO);
errorPane.setWidth100();
errorPane.setHeight100();
errorPane.setContents(detailMsg);
errorPane.setCanSelectText(true);
Canvas debugInfoPane = new Canvas();
debugInfoPane.setWidth100();
debugInfoPane.setAutoHeight();
debugInfoPane.setContents(debugInfo);
debugInfoPane.setCanSelectText(true);
final VLayout detailPane = new VLayout(4);
detailPane.setLayoutMargin(4);
detailPane.setGroupTitle(i18n.ErrorHandler_ButtonDetalis_Title());
detailPane.setIsGroup(true);
detailPane.setVisible(false);
detailPane.addMember(errorPane);
detailPane.addMember(debugInfoPane);
detailPane.setWidth100();
detailPane.setHeight100();
d.addItem(detailPane);
details.addClickHandler(new com.smartgwt.client.widgets.events.ClickHandler() {
@Override
public void onClick(com.smartgwt.client.widgets.events.ClickEvent event) {
if (detailPane.isVisible()) {
d.restore();
} else {
d.maximize();
}
detailPane.setVisible(!detailPane.isVisible());
}
});
}
d.show();
}
/**
* Creates response object from JSON response string in
* {@link com.smartgwt.client.data.RestDataSource SmartGWT format}.
* @param response JSON response string
* @return response object
*/
public static DSResponse getDsResponse(String response) {
DSResponse dsResponse;
// ClientUtils.info(LOG, "response: %s", response);
if (response == null || response.isEmpty()) {
// not JSON response
LOG.log(Level.WARNING, null, new IllegalStateException("Empty response!"));
dsResponse = new DSResponse();
dsResponse.setStatus(RPCResponse.STATUS_SUCCESS);
} else {
JSONValue rVal = JSONParser.parseStrict(response);
if (rVal.isObject() != null) {
rVal = rVal.isObject().get("response");
}
if (rVal != null && rVal.isObject() != null) {
JSONObject rObj = rVal.isObject();
dsResponse = DSResponse.getOrCreateRef(rObj.getJavaScriptObject());
} else {
// not JSON response in expected format
JSONObject jsonObject = new JSONObject();
jsonObject.put("data", new JSONString(response));
dsResponse = new DSResponse(jsonObject.getJavaScriptObject());
dsResponse.setStatus(RPCResponse.STATUS_FAILURE);
}
}
return dsResponse;
}
/**
* Holds parameters from last {@link HandleTransportErrorCallback#handleTransportError
* transport error} that are not accessible from {@link DSResponse}.
* Particularly {@code httpResponseText}.
*/
public static final class TransportError {
private int status;
private int httpResponseCode;
private int transactionNum;
private String httpResponseText;
public TransportError(int transactionNum, int status, int httpResponseCode, String httpResponseText) {
this.transactionNum = transactionNum;
this.status = status;
this.httpResponseCode = httpResponseCode;
this.httpResponseText = httpResponseText;
}
public int getStatus() {
return status;
}
public int getHttpResponseCode() {
return httpResponseCode;
}
public int getTransactionNum() {
return transactionNum;
}
public String getHttpResponseText() {
return httpResponseText;
}
@Override
public String toString() {
return "TransportError{" + "status=" + status
+ ", httpResponseCode=" + httpResponseCode
+ ", transactionNum=" + transactionNum
+ ", httpResponseText=" + httpResponseText
+ '}';
}
}
}