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

[java] remove unused code from ErrorHandler #14195

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 4 additions & 303 deletions java/src/org/openqa/selenium/remote/ErrorHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,66 +17,12 @@

package org.openqa.selenium.remote;

import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.openqa.selenium.UnhandledAlertException;
import org.openqa.selenium.WebDriverException;

/** Maps exceptions to status codes for sending over the wire. */
/** A helper to throw decoded exceptions. */
public class ErrorHandler {

private static final String MESSAGE = "message";
private static final String SCREEN_SHOT = "screen";
private static final String CLASS = "class";
private static final String STACK_TRACE = "stackTrace";
private static final String LINE_NUMBER = "lineNumber";
private static final String METHOD_NAME = "methodName";
private static final String CLASS_NAME = "className";
private static final String FILE_NAME = "fileName";
private static final String UNKNOWN_CLASS = "<anonymous class>";
private static final String UNKNOWN_METHOD = "<anonymous method>";
private static final String UNKNOWN_FILE = null;

private final ErrorCodes errorCodes;

private boolean includeServerErrors;

public ErrorHandler() {
this(true);
}

/**
* @param includeServerErrors Whether to include server-side details in thrown exceptions if the
* information is available.
*/
public ErrorHandler(boolean includeServerErrors) {
this.includeServerErrors = includeServerErrors;
this.errorCodes = new ErrorCodes();
}

/**
* @param includeServerErrors Whether to include server-side details in thrown exceptions if the
* information is available.
* @param codes The ErrorCodes object to use for linking error codes to exceptions.
*/
public ErrorHandler(ErrorCodes codes, boolean includeServerErrors) {
this.includeServerErrors = includeServerErrors;
this.errorCodes = codes;
}

public boolean isIncludeServerErrors() {
return includeServerErrors;
}

public void setIncludeServerErrors(boolean includeServerErrors) {
this.includeServerErrors = includeServerErrors;
}
public ErrorHandler() {}

@SuppressWarnings("unchecked")
public Response throwIfResponseFailed(Response response, long duration) throws RuntimeException {
Expand All @@ -92,254 +38,9 @@ public Response throwIfResponseFailed(Response response, long duration) throws R
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
}
throw new RuntimeException(throwable);
throw new WebDriverException(throwable);
}

Class<? extends WebDriverException> outerErrorType =
errorCodes.getExceptionType(response.getStatus());

Object value = response.getValue();
String message = null;
Throwable cause = null;

if (value instanceof Map) {
Map<String, Object> rawErrorData = (Map<String, Object>) value;
if (!rawErrorData.containsKey(MESSAGE) && rawErrorData.containsKey("value")) {
try {
rawErrorData = (Map<String, Object>) rawErrorData.get("value");
} catch (ClassCastException cce) {
message = String.valueOf(cce);
}
}
try {
message = (String) rawErrorData.get(MESSAGE);
} catch (ClassCastException e) {
// Ok, try to recover gracefully.
message = String.valueOf(e);
}

Throwable serverError = rebuildServerError(rawErrorData, response.getStatus());

// If serverError is null, then the server did not provide a className (only expected if
// the server is a Java process) or a stack trace. The lack of a className is OK, but
// not having a stacktrace really hurts our ability to debug problems.
if (serverError == null) {
if (includeServerErrors) {
// TODO: this should probably link to a wiki article with more info.
message += " (WARNING: The server did not provide any stacktrace information)";
}
} else if (!includeServerErrors) {
// TODO: wiki article with more info.
message += " (WARNING: The client has suppressed server-side stacktraces)";
} else {
cause = serverError;
if (cause.getStackTrace() == null || cause.getStackTrace().length == 0) {
message += " (WARNING: The server did not provide any stacktrace information)";
}
}

if (rawErrorData.get(SCREEN_SHOT) != null) {
cause = new ScreenshotException(String.valueOf(rawErrorData.get(SCREEN_SHOT)), cause);
}
} else if (value != null) {
message = String.valueOf(value);
}

String duration1 = duration(duration);

if (message != null && !message.contains(duration1)) {
message = message + duration1;
}

WebDriverException toThrow = null;

if (outerErrorType.equals(UnhandledAlertException.class) && value instanceof Map) {
toThrow = createUnhandledAlertException(value);
}

if (toThrow == null) {
toThrow =
createThrowable(
outerErrorType,
new Class<?>[] {String.class, Throwable.class, Integer.class},
new Object[] {message, cause, response.getStatus()});
}

if (toThrow == null) {
toThrow =
createThrowable(
outerErrorType,
new Class<?>[] {String.class, Throwable.class},
new Object[] {message, cause});
}

if (toThrow == null) {
toThrow =
createThrowable(outerErrorType, new Class<?>[] {String.class}, new Object[] {message});
}

if (toThrow == null) {
toThrow = new WebDriverException(message, cause);
}

throw toThrow;
}

@SuppressWarnings("unchecked")
private UnhandledAlertException createUnhandledAlertException(Object value) {
Map<String, Object> rawErrorData = (Map<String, Object>) value;
if (rawErrorData.containsKey("alert") || rawErrorData.containsKey("alertText")) {
Object alertText = rawErrorData.get("alertText");
if (alertText == null) {
Map<String, Object> alert = (Map<String, Object>) rawErrorData.get("alert");
if (alert != null) {
alertText = alert.get("text");
}
}
return createThrowable(
UnhandledAlertException.class,
new Class<?>[] {String.class, String.class},
new Object[] {rawErrorData.get("message"), alertText});
}
return null;
}

private String duration(long duration) {
String prefix = "\nCommand duration or timeout: ";
if (duration < 1000) {
return prefix + duration + " milliseconds";
}
return prefix
+ (new BigDecimal(duration).divide(new BigDecimal(1000)).setScale(2, RoundingMode.HALF_UP))
+ " seconds";
}

private <T extends Throwable> T createThrowable(
Class<T> clazz, Class<?>[] parameterTypes, Object[] parameters) {
try {
Constructor<T> constructor = clazz.getConstructor(parameterTypes);
return constructor.newInstance(parameters);
} catch (OutOfMemoryError | ReflectiveOperationException e) {
// Do nothing - fall through.
}
return null;
}

private Throwable rebuildServerError(Map<String, Object> rawErrorData, int responseStatus) {

if (rawErrorData.get(CLASS) == null && rawErrorData.get(STACK_TRACE) == null) {
// Not enough information for us to try to rebuild an error.
return null;
}

Throwable toReturn = null;
String message = (String) rawErrorData.get(MESSAGE);
Class<?> clazz = null;

// First: allow Remote Driver to specify the Selenium Server internal exception
if (rawErrorData.get(CLASS) != null) {
String className = (String) rawErrorData.get(CLASS);
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException ignored) {
// Ok, fall-through
}
}

// If the above fails, map Response Status to Exception class
if (null == clazz) {
clazz = errorCodes.getExceptionType(responseStatus);
}

if (clazz.equals(UnhandledAlertException.class)) {
toReturn = createUnhandledAlertException(rawErrorData);
} else if (Throwable.class.isAssignableFrom(clazz)) {
@SuppressWarnings({"unchecked"})
Class<? extends Throwable> throwableType = (Class<? extends Throwable>) clazz;
toReturn =
createThrowable(throwableType, new Class<?>[] {String.class}, new Object[] {message});
}

if (toReturn == null) {
toReturn = new UnknownServerException(message);
}

// Note: if we have a class name above, we should always have a stack trace.
// The inverse is not always true.
StackTraceElement[] stackTrace = new StackTraceElement[0];
if (rawErrorData.get(STACK_TRACE) != null) {
@SuppressWarnings({"unchecked"})
List<Map<String, Object>> stackTraceInfo =
(List<Map<String, Object>>) rawErrorData.get(STACK_TRACE);

stackTrace =
stackTraceInfo.stream()
.map(entry -> new FrameInfoToStackFrame().apply(entry))
.filter(Objects::nonNull)
.toArray(StackTraceElement[]::new);
}

toReturn.setStackTrace(stackTrace);
return toReturn;
}

/** Exception used as a place holder if the server returns an error without a stack trace. */
public static class UnknownServerException extends WebDriverException {
private UnknownServerException(String s) {
super(s);
}
}

/**
* Function that can rebuild a {@link StackTraceElement} from the frame info included with a
* WebDriver JSON response.
*/
private static class FrameInfoToStackFrame
implements Function<Map<String, Object>, StackTraceElement> {
@Override
public StackTraceElement apply(Map<String, Object> frameInfo) {
if (frameInfo == null) {
return null;
}

Optional<Number> maybeLineNumberInteger = Optional.empty();

final Object lineNumberObject = frameInfo.get(LINE_NUMBER);
if (lineNumberObject instanceof Number) {
maybeLineNumberInteger = Optional.of((Number) lineNumberObject);
} else if (lineNumberObject != null) {
// might be a Number as a String
try {
maybeLineNumberInteger = Optional.of(Integer.parseInt(lineNumberObject.toString()));
} catch (NumberFormatException e) {
maybeLineNumberInteger = Optional.empty();
}
}

// default -1 for unknown, see StackTraceElement constructor javadoc
final int lineNumber = maybeLineNumberInteger.orElse(-1).intValue();

// Gracefully handle remote servers that don't (or can't) send back
// complete stack trace info. At least some of this information should
// be included...
String className =
frameInfo.containsKey(CLASS_NAME)
? toStringOrNull(frameInfo.get(CLASS_NAME))
: UNKNOWN_CLASS;
String methodName =
frameInfo.containsKey(METHOD_NAME)
? toStringOrNull(frameInfo.get(METHOD_NAME))
: UNKNOWN_METHOD;
String fileName =
frameInfo.containsKey(FILE_NAME)
? toStringOrNull(frameInfo.get(FILE_NAME))
: UNKNOWN_FILE;

return new StackTraceElement(className, methodName, fileName, lineNumber);
}

private static String toStringOrNull(Object o) {
return o == null ? null : o.toString();
}
throw new WebDriverException("response failed with unknown status: " + response.getState());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package org.openqa.selenium.remote.codec.w3c;

import static java.net.HttpURLConnection.HTTP_BAD_GATEWAY;
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
import static java.net.HttpURLConnection.HTTP_GATEWAY_TIMEOUT;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static org.openqa.selenium.json.Json.MAP_TYPE;
Expand Down Expand Up @@ -88,15 +87,12 @@ public Response decode(HttpResponse encodedResponse) {
// text"}
if (!encodedResponse.isSuccessful()) {
LOG.fine("Processing an error");
if (HTTP_BAD_METHOD == encodedResponse.getStatus()) {
response.setState("unknown command");
response.setStatus(ErrorCodes.UNKNOWN_COMMAND);
response.setValue(content);
} else if (HTTP_GATEWAY_TIMEOUT == encodedResponse.getStatus()
if (HTTP_GATEWAY_TIMEOUT == encodedResponse.getStatus()
|| HTTP_BAD_GATEWAY == encodedResponse.getStatus()) {
response.setState("unknown error");
response.setStatus(ErrorCodes.UNHANDLED_ERROR);
response.setValue(content);
response.setValue(
new WebDriverException("http gateway error: " + encodedResponse.getStatus()));
} else {
Map<String, Object> org = json.toType(content, MAP_TYPE);
Map<String, Object> obj;
Expand Down
Loading
Loading