View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.struts.flow.core.javascript;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  
21  import org.apache.struts.flow.core.FlowException;
22  import org.apache.struts.flow.core.location.Location;
23  import org.apache.struts.flow.core.location.LocationImpl;
24  import org.apache.struts.flow.core.location.LocationUtils;
25  import org.apache.commons.lang.exception.ExceptionUtils;
26  import org.mozilla.javascript.Context;
27  import org.mozilla.javascript.EcmaError;
28  import org.mozilla.javascript.JavaScriptException;
29  import org.mozilla.javascript.Scriptable;
30  import org.mozilla.javascript.debug.DebugFrame;
31  import org.mozilla.javascript.debug.DebuggableScript;
32  import org.mozilla.javascript.debug.Debugger;
33  
34  /***
35   * A Rhino debugger that tracks location information when an exception is raised in some JavaScript code.
36   * It's purpose is to build a {@link org.apache.flow.core.FlowException} that holds the stacktrace
37   * in the JavaScript code.
38   * <p>
39   * This debugger implementation is designed to be as lightweight and fast as possible, in order to have a
40   * negligible impact on the performances of the Rhino interpreter.
41   * 
42   * @since 2.1.8
43   * @version $Id: LocationTrackingDebugger.java 280811 2005-09-14 09:53:32Z sylvain $
44   */
45  public class LocationTrackingDebugger implements Debugger {
46      
47      // Strong reference to the location finder
48      private static final LocationUtils.LocationFinder rhinoLocFinder = new LocationUtils.LocationFinder() {
49  
50          public Location getLocation(Object obj, String description) {
51              if (obj instanceof EcmaError) {
52                  EcmaError ex = (EcmaError)obj;
53                  if (ex.getSourceName() != null) {
54                      return new LocationImpl(ex.getName(), ex.getSourceName(), ex.getLineNumber(), ex.getColumnNumber());
55                  } else {
56                      return Location.UNKNOWN;
57                  }
58      
59              } else if (obj instanceof JavaScriptException) {
60                  JavaScriptException ex = (JavaScriptException)obj;
61                  if (ex.sourceName() != null) {
62                      return new LocationImpl(description, ex.sourceName(), ex.lineNumber(), -1);
63                  } else {
64                      return Location.UNKNOWN;
65                  }
66              }
67              
68              return null;
69          } 
70      };
71  
72      
73      static {
74          // Register what's needed to analyze exceptions produced by Rhino
75          ExceptionUtils.addCauseMethodName("getWrappedException");
76          LocationUtils.addFinder(rhinoLocFinder);
77      }
78      
79      private List locations;
80      private Throwable throwable;
81      
82      public void handleCompilationDone(Context cx, DebuggableScript fnOrScript, String source) {
83          // nothing
84      }
85  
86      public DebugFrame getFrame(Context cx, DebuggableScript fnOrScript) {
87          return new StackTrackingFrame(fnOrScript);
88      }
89  
90      /***
91       * Get an exception that reflects the known location stack
92       *
93       * @param description a description for the exception
94       * @param originalException the original exception
95       * 
96       * @return a suitable exception to throw
97       * @see ProcessingException#throwLocated(String, Throwable, Location)
98       */
99      public Exception getException(String description, Exception originalException) throws FlowException {
100         if (throwable == null || locations == null) {
101             // Cannot to better for now
102             return originalException;
103         }
104 
105         // Unwrap original exception, if any, wrapped by Rhino (the wrapping
106         // class is different in Rhino+cont and Rhino 1.6)
107         Throwable cause = ExceptionUtils.getCause(throwable);
108         if (cause != null)
109             throwable = cause;
110 
111         return FlowException.throwLocated(description, throwable, locations);
112     }
113 
114     private class StackTrackingFrame implements DebugFrame {
115         
116         DebuggableScript script;
117         int line;
118 
119         public StackTrackingFrame(DebuggableScript script) {
120             this.script = script;
121         }
122         
123         public void onEnter(Context cx, Scriptable activation, Scriptable thisObj, Object[] args) {
124             // nothing
125         }
126         
127         public void onLineChange(Context cx, int lineNumber) {
128             line = lineNumber;
129         }
130 
131         public void onExceptionThrown(Context cx, Throwable ex) {
132             throwable = ex;
133         }
134 
135         public void onExit(Context cx, boolean byThrow, Object resultOrException) {
136             if (byThrow) {
137                 String name = null;
138                 if (script.isFunction()) {
139                     name = script.getFunctionName();
140                 } else {
141                     name = "[script]";
142                 }
143 
144                 if (locations == null) {
145                     locations = new ArrayList(1); // start small
146                 }
147 
148                 locations.add(new LocationImpl(name, script.getSourceName(), line, -1));
149 
150             } else if (locations != null) {
151                 // The exception was handled by the script: clear any recorded locations
152                 locations = null;
153             }
154         }
155     }
156 }
157