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