View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package javax.faces.webapp;
20  
21  import java.beans.BeanInfo;
22  import java.beans.Introspector;
23  import java.beans.PropertyDescriptor;
24  import java.io.ByteArrayOutputStream;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.PrintWriter;
29  import java.io.StringWriter;
30  import java.io.Writer;
31  import java.lang.reflect.Method;
32  import java.text.DateFormat;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import java.util.Collection;
36  import java.util.Date;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.SortedMap;
41  import java.util.TreeMap;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  
45  import javax.faces.component.UIComponent;
46  import javax.faces.context.ExternalContext;
47  import javax.faces.context.FacesContext;
48  import javax.faces.el.MethodBinding;
49  import javax.faces.el.ValueBinding;
50  import javax.servlet.ServletException;
51  import javax.servlet.http.HttpServletResponse;
52  
53  import org.apache.commons.logging.Log;
54  import org.apache.commons.logging.LogFactory;
55  
56  /**
57   * @author Jacob Hookom (ICLA with ASF filed)
58   */
59  class _ErrorPageWriter {
60  
61      private static final Log log = LogFactory.getLog(_ErrorPageWriter.class);
62      private static final Class[] NO_ARGS = new Class[0];
63  
64      private final static String TS = "<";
65  
66      private static final String ERROR_TEMPLATE = "META-INF/rsc/myfaces-dev-error.xml";
67  
68      private static final String ERROR_TEMPLATE_RESOURCE = "org.apache.myfaces.ERROR_TEMPLATE_RESOURCE";
69  
70      private static String[] ERROR_PARTS;
71  
72      private static final String DEBUG_TEMPLATE = "META-INF/rsc/myfaces-dev-debug.xml";
73      
74      private static final String DEBUG_TEMPLATE_RESOURCE = "org.apache.myfaces.DEBUG_TEMPLATE_RESOURCE";    
75  
76      private static String[] DEBUG_PARTS;
77  
78      public _ErrorPageWriter() {
79          super();
80      }
81      
82      private static String getErrorTemplate(FacesContext context)
83      {
84          String errorTemplate = context.getExternalContext().getInitParameter(ERROR_TEMPLATE_RESOURCE);
85          if (errorTemplate != null)
86          {
87              return errorTemplate;
88          }
89          return ERROR_TEMPLATE;
90      }
91      
92      private static String getDebugTemplate(FacesContext context)
93      {
94          String debugTemplate = context.getExternalContext().getInitParameter(DEBUG_TEMPLATE_RESOURCE);
95          if (debugTemplate != null)
96          {
97              return debugTemplate;
98          }        
99          return DEBUG_TEMPLATE;
100     }
101     
102     private static void init(FacesContext context) throws IOException {
103         if (ERROR_PARTS == null) {
104             ERROR_PARTS = splitTemplate(getErrorTemplate(context));
105         }
106 
107         if (DEBUG_PARTS == null) {
108             DEBUG_PARTS = splitTemplate(getDebugTemplate(context));
109         }
110     }
111 
112     private static String[] splitTemplate(String rsc) throws IOException {
113         InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(rsc);
114         if (is == null) {
115             throw new FileNotFoundException(rsc);
116         }
117         ByteArrayOutputStream baos = new ByteArrayOutputStream();
118         byte[] buff = new byte[512];
119         int read;
120         while ((read = is.read(buff)) != -1) {
121             baos.write(buff, 0, read);
122         }
123         String str = baos.toString();
124         return str.split("@@");
125     }
126 
127     private static ArrayList getErrorId(Throwable e){
128         String message = e.getMessage();
129 
130         if(message==null)
131             return null;
132 
133         ArrayList list = new ArrayList();
134         Pattern pattern = Pattern.compile(".*?\\Q,Id:\\E\\s*(\\S+)\\s*\\].*?");
135         Matcher matcher = pattern.matcher(message);
136 
137         while (matcher.find()){
138             list.add(matcher.group(1));
139         }
140         if (list.size()>0) return list;
141         return null;
142     }
143 
144     public static void writeCause(Writer writer, Throwable ex) throws IOException {
145         String msg = ex.getMessage();
146         for(;;) {
147             Throwable t = getCause(ex);
148             if (t == null)
149                 break;
150             
151             ex = t;
152             if (ex.getMessage()!=null) msg = ex.getMessage();
153         }
154 
155         if (msg != null) {
156             msg =ex.getClass().getName() + " - " + msg;
157             writer.write(msg.replaceAll("<", TS));
158         } else {
159             writer.write(ex.getClass().getName());
160         }
161     }
162 
163     public static void debugHtml(Writer writer, FacesContext faces, Throwable e) throws IOException {
164         init(faces);
165         Date now = new Date();
166         for (int i = 0; i < ERROR_PARTS.length; i++) {
167             if ("message".equals(ERROR_PARTS[i])) {
168                 String msg = e.getMessage();
169                 if (msg != null) {
170                     writer.write(msg.replaceAll("<", TS));
171                 } else {
172                     writer.write(e.getClass().getName());
173                 }
174             } else if ("trace".equals(ERROR_PARTS[i])) {
175                 writeException(writer, e);
176             } else if ("now".equals(ERROR_PARTS[i])) {
177                 writer.write(DateFormat.getDateTimeInstance().format(now));
178             } else if ("tree".equals(ERROR_PARTS[i])) {
179                 if (faces.getViewRoot() != null) {
180                     writeComponent(writer, faces.getViewRoot(), getErrorId(e));
181                 }
182             } else if ("vars".equals(ERROR_PARTS[i])) {
183                 writeVariables(writer, faces);
184             } else if ("cause".equals(ERROR_PARTS[i])) {
185                 writeCause(writer, e);
186             } else {
187                 writer.write(ERROR_PARTS[i]);
188             }
189         }
190     }
191     
192     public static void debugHtml(Writer writer, FacesContext faces, List exceptionList) throws IOException
193     {
194         init(faces);
195         Date now = new Date();
196         for (int i = 0; i < ERROR_PARTS.length; i++)
197         {
198             if ("message".equals(ERROR_PARTS[i]))
199             {
200                 for (int j = 0; j < exceptionList.size(); j++)
201                 {
202                     Exception e = (Exception) exceptionList.get(j);
203                     String msg = e.getMessage();
204                     if (msg != null)
205                     {
206                         writer.write(msg.replaceAll("<", TS));
207                     }
208                     else 
209                     {
210                         writer.write(e.getClass().getName());
211                     }
212                     if (!(j+1==exceptionList.size()))
213                     {
214                         writer.write("<br>");
215                     }
216                 }
217             }
218             else if ("trace".equals(ERROR_PARTS[i]))
219             {
220                 for (int j = 0; j < exceptionList.size(); j++)
221                 {
222                     Exception e = (Exception) exceptionList.get(j);
223                     writeException(writer, e);
224                 }
225             }
226             else if ("now".equals(ERROR_PARTS[i]))
227             {
228                 writer.write(DateFormat.getDateTimeInstance().format(now));
229             }
230             else if ("tree".equals(ERROR_PARTS[i]))
231             {
232                 if (faces.getViewRoot() != null)
233                 {
234                     List highlightId = null;
235                     for (int j = 0; j < exceptionList.size(); j++)
236                     {
237                         Exception e = (Exception) exceptionList.get(j);
238                         if (highlightId == null)
239                         {
240                             highlightId = getErrorId(e);
241                         }
242                         else
243                         {
244                             highlightId.addAll(getErrorId(e));
245                         }
246                     }
247                     writeComponent(writer, faces.getViewRoot(), highlightId);
248                 }
249             }
250             else if ("vars".equals(ERROR_PARTS[i]))
251             {
252                 writeVariables(writer, faces);
253             }
254             else if ("cause".equals(ERROR_PARTS[i]))
255             {
256                 for (int j = 0; j < exceptionList.size(); j++)
257                 {
258                     Exception e = (Exception) exceptionList.get(j);
259                     writeCause(writer, e);
260                     if (!(j+1==exceptionList.size()))
261                     {
262                         writer.write("<br>");
263                     }
264                 }
265             }
266             else
267             {
268                 writer.write(ERROR_PARTS[i]);
269             }
270         }
271     }    
272 
273     private static void writeException(Writer writer, Throwable e) throws IOException {
274         StringWriter str = new StringWriter(256);
275         PrintWriter pstr = new PrintWriter(str);
276         e.printStackTrace(pstr);
277         pstr.close();
278         writer.write(str.toString().replaceAll("<", TS));
279     }
280 
281     public static void debugHtml(Writer writer, FacesContext faces) throws IOException {
282         init(faces);
283         Date now = new Date();
284         for (int i = 0; i < DEBUG_PARTS.length; i++) {
285             if ("message".equals(DEBUG_PARTS[i])) {
286                 writer.write(faces.getViewRoot().getViewId());
287             } else if ("now".equals(DEBUG_PARTS[i])) {
288                 writer.write(DateFormat.getDateTimeInstance().format(now));
289             } else if ("tree".equals(DEBUG_PARTS[i])) {
290                 writeComponent(writer, faces.getViewRoot(), null);
291             } else if ("vars".equals(DEBUG_PARTS[i])) {
292                 writeVariables(writer, faces);
293             } else {
294                 writer.write(DEBUG_PARTS[i]);
295             }
296         }
297     }
298 
299     private static void writeVariables(Writer writer, FacesContext faces) throws IOException {
300         ExternalContext ctx = faces.getExternalContext();
301         writeVariables(writer, ctx.getRequestParameterMap(), "Request Parameters");
302         writeVariables(writer, ctx.getRequestMap(), "Request Attributes");
303         if (ctx.getSession(false) != null) {
304             writeVariables(writer, ctx.getSessionMap(), "Session Attributes");
305         }
306         writeVariables(writer, ctx.getApplicationMap(), "Application Attributes");
307     }
308 
309     private static void writeVariables(Writer writer, Map vars, String caption) throws IOException {
310         writer.write("<table><caption>");
311         writer.write(caption);
312         writer.write("</caption><thead><tr><th style=\"width: 10%; \">Name</th><th style=\"width: 90%; \">Value</th></tr></thead><tbody>");
313         boolean written = false;
314         if (!vars.isEmpty()) {
315             SortedMap map = new TreeMap(vars);
316             Map.Entry entry = null;
317             String key = null;
318             for (Iterator itr = map.entrySet().iterator(); itr.hasNext(); ) {
319                 entry = (Map.Entry) itr.next();
320                 key = entry.getKey().toString();
321                 if (key.indexOf('.') == -1) {
322                     writer.write("<tr><td>");
323                     writer.write(key.replaceAll("<", TS));
324                     writer.write("</td><td>");
325                     writer.write(entry.getValue().toString().replaceAll("<", TS));
326                     writer.write("</td></tr>");
327                     written = true;
328                 }
329             }
330         }
331         if (!written) {
332             writer.write("<tr><td colspan=\"2\"><em>None</em></td></tr>");
333         }
334         writer.write("</tbody></table>");
335     }
336 
337     private static void writeComponent(Writer writer, UIComponent c, List highlightId) throws IOException {
338         writer.write("<dl><dt");
339         if (isText(c)) {
340             writer.write(" class=\"uicText\"");
341         }
342         if (highlightId != null){
343             if ((highlightId.size() > 0) && (highlightId.get(0).equals(c.getId()))){
344                 highlightId.remove(0);
345                 if (highlightId.size()==0){
346                     writer.write(" class=\"highlightComponent\"");
347                 }
348             }
349         }
350         writer.write(">");
351 
352         boolean hasChildren = c.getChildCount() > 0 || c.getFacets().size() > 0;
353 
354         writeStart(writer, c, hasChildren);
355         writer.write("</dt>");
356         if (hasChildren) {
357             if (c.getFacets().size() > 0) {
358                 Map.Entry entry;
359                 for (Iterator itr = c.getFacets().entrySet().iterator(); itr.hasNext(); ) {
360                     entry = (Map.Entry) itr.next();
361                     writer.write("<dd class=\"uicFacet\">");
362                     writer.write("<span>");
363                     writer.write((String) entry.getKey());
364                     writer.write("</span>");
365                     writeComponent(writer, (UIComponent) entry.getValue(), highlightId);
366                     writer.write("</dd>");
367                 }
368             }
369             if (c.getChildCount() > 0) {
370                 for (Iterator itr = c.getChildren().iterator(); itr.hasNext(); ) {
371                     writer.write("<dd>");
372                     writeComponent(writer, (UIComponent) itr.next(), highlightId);
373                     writer.write("</dd>");
374                 }
375             }
376             writer.write("<dt>");
377             writeEnd(writer, c);
378             writer.write("</dt>");
379         }
380         writer.write("</dl>");
381     }
382 
383     private static void writeEnd(Writer writer, UIComponent c) throws IOException {
384         if (!isText(c)) {
385             writer.write(TS);
386             writer.write('/');
387             writer.write(getName(c));
388             writer.write('>');
389         }
390     }
391 
392     private final static String[] IGNORE = new String[] { "parent", "rendererType" };
393 
394     private static void writeAttributes(Writer writer, UIComponent c) {
395         try {
396             BeanInfo info = Introspector.getBeanInfo(c.getClass());
397             PropertyDescriptor[] pd = info.getPropertyDescriptors();
398             Method m = null;
399             Object v = null;
400             String str = null;
401             for (int i = 0; i < pd.length; i++) {
402                 if (pd[i].getWriteMethod() != null && Arrays.binarySearch(IGNORE, pd[i].getName()) < 0) {
403                     m = pd[i].getReadMethod();
404                     try {
405                         v = m.invoke(c, null);
406                         if (v != null) {
407                             if (v instanceof Collection || v instanceof Map || v instanceof Iterator) {
408                                 continue;
409                             }
410                             writer.write(" ");
411                             writer.write(pd[i].getName());
412                             writer.write("=\"");
413                             if (v instanceof ValueBinding) {
414                                 str = ((ValueBinding) v).getExpressionString();
415                             } else if (v instanceof MethodBinding) {
416                                 str = ((MethodBinding) v).getExpressionString();
417                             } else {
418                                 ValueBinding vb = c.getValueBinding(pd[i].getName());
419                                 str = vb!=null?(vb.getExpressionString()+"="+v.toString()):v.toString();
420                             }
421                             writer.write(str.replaceAll("<", TS));
422                             writer.write("\"");
423                         }
424                     } catch (Exception e) {
425                         // do nothing
426                     }
427                 }
428             }
429 
430             ValueBinding binding = c.getValueBinding("binding");
431             if (binding != null) {
432                 writer.write(" binding=\"");
433                 writer.write(binding.getExpressionString().replaceAll("<", TS));
434                 writer.write("\"");
435             }
436         } catch (Exception e) {
437             // do nothing
438         }
439     }
440 
441     private static void writeStart(Writer writer, UIComponent c, boolean children) throws IOException {
442         if (isText(c)) {
443             String str = c.toString().trim();
444             writer.write(str.replaceAll("<", TS));
445         } else {
446             writer.write(TS);
447             writer.write(getName(c));
448             writeAttributes(writer, c);
449             if (children) {
450                 writer.write('>');
451             } else {
452                 writer.write("/>");
453             }
454         }
455     }
456 
457     private static String getName(UIComponent c) {
458         String nm = c.getClass().getName();
459         return nm.substring(nm.lastIndexOf('.') + 1);
460     }
461 
462     private static boolean isText(UIComponent c) {
463         return (c.getClass().getName().startsWith("com.sun.facelets.compiler"));
464     }
465 
466     public static void handleException(FacesContext facesContext, Exception ex) throws ServletException, IOException
467     {
468         handleThrowable(facesContext, ex);
469     }
470     
471     public static void handleThrowable(FacesContext facesContext, Throwable ex) throws ServletException, IOException {
472 
473         prepareExceptionStack(ex);
474 
475         Object response = facesContext.getExternalContext().getResponse();
476         if(response instanceof HttpServletResponse) {
477             HttpServletResponse httpResp = (HttpServletResponse) response;
478             if (!httpResp.isCommitted()) {
479                 httpResp.reset();
480                 httpResp.setContentType("text/html; charset=UTF-8");
481                 Writer writer = httpResp.getWriter();
482 
483                 debugHtml(writer, facesContext, ex);
484 
485                 log.error("An exception occurred", ex);
486             }
487             else {
488                 throwException(ex);
489             }
490         }
491         else {
492             throwException(ex);
493         }
494     }
495     
496     public static void handleExceptionList(FacesContext facesContext, List exceptionList) throws ServletException, IOException
497     {
498         for (int i = 0; i < exceptionList.size(); i++)
499         {
500             prepareExceptionStack( (Exception) exceptionList.get(i));
501         }
502 
503         Object response = facesContext.getExternalContext().getResponse();
504         if(response instanceof HttpServletResponse)
505         {
506             HttpServletResponse httpResp = (HttpServletResponse) response;
507             if (!httpResp.isCommitted())
508             {
509                 httpResp.reset();
510                 httpResp.setContentType("text/html; charset=UTF-8");
511                 Writer writer = httpResp.getWriter();
512 
513                 debugHtml(writer, facesContext, exceptionList);
514 
515                 for (int i = 0; i < exceptionList.size(); i++)
516                 {
517                     log.error("An exception occurred", (Exception) exceptionList.get(i));
518                 }
519             }
520             else
521             {
522                 throwException((Exception)exceptionList.get(0));
523             }
524         }
525         else
526         {
527             throwException((Exception)exceptionList.get(0));
528         }
529     }
530 
531     private static void prepareExceptionStack(Throwable ex) {
532 
533         if(ex==null)
534             return;
535 
536         //check for getRootCause and getCause-methods
537         if(!initCausePerReflection(ex,"getRootCause")) {
538            initCausePerReflection(ex,"getCause");
539         }
540 
541         prepareExceptionStack(getCause(ex));
542     }
543 
544     /**
545      * Get the cause of an exception, if available. Reflection must be used because
546      * JSF11 supports java1.3 but Throwable.getCause was added in java1.4.
547      */
548     private static Throwable getCause(Throwable ex) {
549         try {
550             Method causeGetter = ex.getClass().getMethod("getCause", NO_ARGS);
551             Throwable cause = (Throwable) causeGetter.invoke(ex, NO_ARGS);
552             return cause;
553         } catch (Exception e1) {
554             return null;
555         }
556     }
557     
558     private static boolean initCausePerReflection(Throwable ex, String methodName) {
559         try {
560             Method causeGetter = ex.getClass().getMethod(methodName, NO_ARGS);
561             Throwable rootCause = (Throwable) causeGetter.invoke(ex, NO_ARGS);
562             return initCauseIfAvailable(ex,rootCause);
563         } catch (Exception e1) {
564             return false;
565         }
566     }
567 
568     static void throwException(Throwable e) throws IOException, ServletException {
569 
570         prepareExceptionStack(e);
571 
572         if (e instanceof IOException)
573         {
574             throw (IOException)e;
575         }
576         else if (e instanceof ServletException)
577         {
578             throw (ServletException)e;
579         }
580         else
581         {
582             ServletException ex;
583 
584             if (e.getMessage() != null) {
585                 ex=new ServletException(e.getMessage(), e);
586             }
587             else {
588                 ex=new ServletException(e);
589             }
590 
591             initCauseIfAvailable(ex, e);
592 
593             throw ex;
594         }
595     }
596 
597     private static boolean initCauseIfAvailable(Throwable th, Throwable cause) {
598 
599         if(cause == null)
600             return false;
601 
602         try {
603             Method m = Throwable.class.getMethod("initCause",new Class[]{Throwable.class});
604             m.invoke(th,new Object[]{cause});
605             return true;
606         }
607         catch(Exception e) {
608             return false;
609         }
610     }
611 }
612