1717
1818package org .openqa .selenium .remote ;
1919
20- import java .lang .reflect .Constructor ;
21- import java .math .BigDecimal ;
22- import java .math .RoundingMode ;
23- import java .util .List ;
24- import java .util .Map ;
25- import java .util .Objects ;
26- import java .util .Optional ;
27- import java .util .function .Function ;
28- import org .openqa .selenium .UnhandledAlertException ;
2920import org .openqa .selenium .WebDriverException ;
3021
31- /** Maps exceptions to status codes for sending over the wire . */
22+ /** A helper to throw decoded exceptions . */
3223public class ErrorHandler {
3324
34- private static final String MESSAGE = "message" ;
35- private static final String SCREEN_SHOT = "screen" ;
36- private static final String CLASS = "class" ;
37- private static final String STACK_TRACE = "stackTrace" ;
38- private static final String LINE_NUMBER = "lineNumber" ;
39- private static final String METHOD_NAME = "methodName" ;
40- private static final String CLASS_NAME = "className" ;
41- private static final String FILE_NAME = "fileName" ;
42- private static final String UNKNOWN_CLASS = "<anonymous class>" ;
43- private static final String UNKNOWN_METHOD = "<anonymous method>" ;
44- private static final String UNKNOWN_FILE = null ;
45-
46- private final ErrorCodes errorCodes ;
47-
48- private boolean includeServerErrors ;
49-
50- public ErrorHandler () {
51- this (true );
52- }
53-
54- /**
55- * @param includeServerErrors Whether to include server-side details in thrown exceptions if the
56- * information is available.
57- */
58- public ErrorHandler (boolean includeServerErrors ) {
59- this .includeServerErrors = includeServerErrors ;
60- this .errorCodes = new ErrorCodes ();
61- }
62-
63- /**
64- * @param includeServerErrors Whether to include server-side details in thrown exceptions if the
65- * information is available.
66- * @param codes The ErrorCodes object to use for linking error codes to exceptions.
67- */
68- public ErrorHandler (ErrorCodes codes , boolean includeServerErrors ) {
69- this .includeServerErrors = includeServerErrors ;
70- this .errorCodes = codes ;
71- }
72-
73- public boolean isIncludeServerErrors () {
74- return includeServerErrors ;
75- }
76-
77- public void setIncludeServerErrors (boolean includeServerErrors ) {
78- this .includeServerErrors = includeServerErrors ;
79- }
25+ public ErrorHandler () {}
8026
8127 @ SuppressWarnings ("unchecked" )
8228 public Response throwIfResponseFailed (Response response , long duration ) throws RuntimeException {
@@ -92,254 +38,9 @@ public Response throwIfResponseFailed(Response response, long duration) throws R
9238 if (throwable instanceof RuntimeException ) {
9339 throw (RuntimeException ) throwable ;
9440 }
95- throw new RuntimeException (throwable );
41+ throw new WebDriverException (throwable );
9642 }
9743
98- Class <? extends WebDriverException > outerErrorType =
99- errorCodes .getExceptionType (response .getStatus ());
100-
101- Object value = response .getValue ();
102- String message = null ;
103- Throwable cause = null ;
104-
105- if (value instanceof Map ) {
106- Map <String , Object > rawErrorData = (Map <String , Object >) value ;
107- if (!rawErrorData .containsKey (MESSAGE ) && rawErrorData .containsKey ("value" )) {
108- try {
109- rawErrorData = (Map <String , Object >) rawErrorData .get ("value" );
110- } catch (ClassCastException cce ) {
111- message = String .valueOf (cce );
112- }
113- }
114- try {
115- message = (String ) rawErrorData .get (MESSAGE );
116- } catch (ClassCastException e ) {
117- // Ok, try to recover gracefully.
118- message = String .valueOf (e );
119- }
120-
121- Throwable serverError = rebuildServerError (rawErrorData , response .getStatus ());
122-
123- // If serverError is null, then the server did not provide a className (only expected if
124- // the server is a Java process) or a stack trace. The lack of a className is OK, but
125- // not having a stacktrace really hurts our ability to debug problems.
126- if (serverError == null ) {
127- if (includeServerErrors ) {
128- // TODO: this should probably link to a wiki article with more info.
129- message += " (WARNING: The server did not provide any stacktrace information)" ;
130- }
131- } else if (!includeServerErrors ) {
132- // TODO: wiki article with more info.
133- message += " (WARNING: The client has suppressed server-side stacktraces)" ;
134- } else {
135- cause = serverError ;
136- if (cause .getStackTrace () == null || cause .getStackTrace ().length == 0 ) {
137- message += " (WARNING: The server did not provide any stacktrace information)" ;
138- }
139- }
140-
141- if (rawErrorData .get (SCREEN_SHOT ) != null ) {
142- cause = new ScreenshotException (String .valueOf (rawErrorData .get (SCREEN_SHOT )), cause );
143- }
144- } else if (value != null ) {
145- message = String .valueOf (value );
146- }
147-
148- String duration1 = duration (duration );
149-
150- if (message != null && !message .contains (duration1 )) {
151- message = message + duration1 ;
152- }
153-
154- WebDriverException toThrow = null ;
155-
156- if (outerErrorType .equals (UnhandledAlertException .class ) && value instanceof Map ) {
157- toThrow = createUnhandledAlertException (value );
158- }
159-
160- if (toThrow == null ) {
161- toThrow =
162- createThrowable (
163- outerErrorType ,
164- new Class <?>[] {String .class , Throwable .class , Integer .class },
165- new Object [] {message , cause , response .getStatus ()});
166- }
167-
168- if (toThrow == null ) {
169- toThrow =
170- createThrowable (
171- outerErrorType ,
172- new Class <?>[] {String .class , Throwable .class },
173- new Object [] {message , cause });
174- }
175-
176- if (toThrow == null ) {
177- toThrow =
178- createThrowable (outerErrorType , new Class <?>[] {String .class }, new Object [] {message });
179- }
180-
181- if (toThrow == null ) {
182- toThrow = new WebDriverException (message , cause );
183- }
184-
185- throw toThrow ;
186- }
187-
188- @ SuppressWarnings ("unchecked" )
189- private UnhandledAlertException createUnhandledAlertException (Object value ) {
190- Map <String , Object > rawErrorData = (Map <String , Object >) value ;
191- if (rawErrorData .containsKey ("alert" ) || rawErrorData .containsKey ("alertText" )) {
192- Object alertText = rawErrorData .get ("alertText" );
193- if (alertText == null ) {
194- Map <String , Object > alert = (Map <String , Object >) rawErrorData .get ("alert" );
195- if (alert != null ) {
196- alertText = alert .get ("text" );
197- }
198- }
199- return createThrowable (
200- UnhandledAlertException .class ,
201- new Class <?>[] {String .class , String .class },
202- new Object [] {rawErrorData .get ("message" ), alertText });
203- }
204- return null ;
205- }
206-
207- private String duration (long duration ) {
208- String prefix = "\n Command duration or timeout: " ;
209- if (duration < 1000 ) {
210- return prefix + duration + " milliseconds" ;
211- }
212- return prefix
213- + (new BigDecimal (duration ).divide (new BigDecimal (1000 )).setScale (2 , RoundingMode .HALF_UP ))
214- + " seconds" ;
215- }
216-
217- private <T extends Throwable > T createThrowable (
218- Class <T > clazz , Class <?>[] parameterTypes , Object [] parameters ) {
219- try {
220- Constructor <T > constructor = clazz .getConstructor (parameterTypes );
221- return constructor .newInstance (parameters );
222- } catch (OutOfMemoryError | ReflectiveOperationException e ) {
223- // Do nothing - fall through.
224- }
225- return null ;
226- }
227-
228- private Throwable rebuildServerError (Map <String , Object > rawErrorData , int responseStatus ) {
229-
230- if (rawErrorData .get (CLASS ) == null && rawErrorData .get (STACK_TRACE ) == null ) {
231- // Not enough information for us to try to rebuild an error.
232- return null ;
233- }
234-
235- Throwable toReturn = null ;
236- String message = (String ) rawErrorData .get (MESSAGE );
237- Class <?> clazz = null ;
238-
239- // First: allow Remote Driver to specify the Selenium Server internal exception
240- if (rawErrorData .get (CLASS ) != null ) {
241- String className = (String ) rawErrorData .get (CLASS );
242- try {
243- clazz = Class .forName (className );
244- } catch (ClassNotFoundException ignored ) {
245- // Ok, fall-through
246- }
247- }
248-
249- // If the above fails, map Response Status to Exception class
250- if (null == clazz ) {
251- clazz = errorCodes .getExceptionType (responseStatus );
252- }
253-
254- if (clazz .equals (UnhandledAlertException .class )) {
255- toReturn = createUnhandledAlertException (rawErrorData );
256- } else if (Throwable .class .isAssignableFrom (clazz )) {
257- @ SuppressWarnings ({"unchecked" })
258- Class <? extends Throwable > throwableType = (Class <? extends Throwable >) clazz ;
259- toReturn =
260- createThrowable (throwableType , new Class <?>[] {String .class }, new Object [] {message });
261- }
262-
263- if (toReturn == null ) {
264- toReturn = new UnknownServerException (message );
265- }
266-
267- // Note: if we have a class name above, we should always have a stack trace.
268- // The inverse is not always true.
269- StackTraceElement [] stackTrace = new StackTraceElement [0 ];
270- if (rawErrorData .get (STACK_TRACE ) != null ) {
271- @ SuppressWarnings ({"unchecked" })
272- List <Map <String , Object >> stackTraceInfo =
273- (List <Map <String , Object >>) rawErrorData .get (STACK_TRACE );
274-
275- stackTrace =
276- stackTraceInfo .stream ()
277- .map (entry -> new FrameInfoToStackFrame ().apply (entry ))
278- .filter (Objects ::nonNull )
279- .toArray (StackTraceElement []::new );
280- }
281-
282- toReturn .setStackTrace (stackTrace );
283- return toReturn ;
284- }
285-
286- /** Exception used as a place holder if the server returns an error without a stack trace. */
287- public static class UnknownServerException extends WebDriverException {
288- private UnknownServerException (String s ) {
289- super (s );
290- }
291- }
292-
293- /**
294- * Function that can rebuild a {@link StackTraceElement} from the frame info included with a
295- * WebDriver JSON response.
296- */
297- private static class FrameInfoToStackFrame
298- implements Function <Map <String , Object >, StackTraceElement > {
299- @ Override
300- public StackTraceElement apply (Map <String , Object > frameInfo ) {
301- if (frameInfo == null ) {
302- return null ;
303- }
304-
305- Optional <Number > maybeLineNumberInteger = Optional .empty ();
306-
307- final Object lineNumberObject = frameInfo .get (LINE_NUMBER );
308- if (lineNumberObject instanceof Number ) {
309- maybeLineNumberInteger = Optional .of ((Number ) lineNumberObject );
310- } else if (lineNumberObject != null ) {
311- // might be a Number as a String
312- try {
313- maybeLineNumberInteger = Optional .of (Integer .parseInt (lineNumberObject .toString ()));
314- } catch (NumberFormatException e ) {
315- maybeLineNumberInteger = Optional .empty ();
316- }
317- }
318-
319- // default -1 for unknown, see StackTraceElement constructor javadoc
320- final int lineNumber = maybeLineNumberInteger .orElse (-1 ).intValue ();
321-
322- // Gracefully handle remote servers that don't (or can't) send back
323- // complete stack trace info. At least some of this information should
324- // be included...
325- String className =
326- frameInfo .containsKey (CLASS_NAME )
327- ? toStringOrNull (frameInfo .get (CLASS_NAME ))
328- : UNKNOWN_CLASS ;
329- String methodName =
330- frameInfo .containsKey (METHOD_NAME )
331- ? toStringOrNull (frameInfo .get (METHOD_NAME ))
332- : UNKNOWN_METHOD ;
333- String fileName =
334- frameInfo .containsKey (FILE_NAME )
335- ? toStringOrNull (frameInfo .get (FILE_NAME ))
336- : UNKNOWN_FILE ;
337-
338- return new StackTraceElement (className , methodName , fileName , lineNumber );
339- }
340-
341- private static String toStringOrNull (Object o ) {
342- return o == null ? null : o .toString ();
343- }
44+ throw new WebDriverException ("response failed with unknown status: " + response .getState ());
34445 }
34546}
0 commit comments