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