001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.html.pages;
018
019import java.util.List;
020
021import javax.servlet.http.HttpServletResponse;
022
023import org.apache.wicket.Page;
024import org.apache.wicket.WicketRuntimeException;
025import org.apache.wicket.markup.MarkupException;
026import org.apache.wicket.markup.MarkupStream;
027import org.apache.wicket.markup.html.WebMarkupContainer;
028import org.apache.wicket.markup.html.basic.Label;
029import org.apache.wicket.markup.html.basic.MultiLineLabel;
030import org.apache.wicket.markup.html.debug.PageView;
031import org.apache.wicket.markup.html.link.Link;
032import org.apache.wicket.request.http.WebResponse;
033import org.apache.wicket.util.lang.Generics;
034
035
036/**
037 * Shows a runtime exception on a nice HTML page.
038 * 
039 * @author Jonathan Locke
040 */
041public class ExceptionErrorPage extends AbstractErrorPage
042{
043        private static final long serialVersionUID = 1L;
044
045        /** Keep a reference to the root cause. WicketTester will use it */
046        private final transient Throwable throwable;
047
048        /**
049         * Constructor.
050         * 
051         * @param throwable
052         *            The exception to show
053         * @param page
054         *            The page being rendered when the exception was thrown
055         */
056        public ExceptionErrorPage(final Throwable throwable, final Page page)
057        {
058                this.throwable = throwable;
059
060                // Add exception label
061                add(new MultiLineLabel("exception", getErrorMessage(throwable)));
062
063                add(new MultiLineLabel("stacktrace", getStackTrace(throwable)));
064
065                // Get values
066                String resource = "";
067                String markup = "";
068                MarkupStream markupStream = null;
069
070                if (throwable instanceof MarkupException)
071                {
072                        markupStream = ((MarkupException)throwable).getMarkupStream();
073
074                        if (markupStream != null)
075                        {
076                                markup = markupStream.toHtmlDebugString();
077                                resource = markupStream.getResource().toString();
078                        }
079                }
080
081                // Create markup label
082                final MultiLineLabel markupLabel = new MultiLineLabel("markup", markup);
083
084                markupLabel.setEscapeModelStrings(false);
085
086                // Add container with markup highlighted
087                final WebMarkupContainer markupHighlight = new WebMarkupContainer("markupHighlight");
088
089                markupHighlight.add(markupLabel);
090                markupHighlight.add(new Label("resource", resource));
091                add(markupHighlight);
092
093                // Show container if markup stream is available
094                markupHighlight.setVisible(markupStream != null);
095
096                add(new Link<Void>("displayPageViewLink")
097                {
098                        private static final long serialVersionUID = 1L;
099
100                        @Override
101                        public void onClick()
102                        {
103                                ExceptionErrorPage.this.replace(new PageView("componentTree", page));
104                                setVisible(false);
105                        }
106                });
107
108                add(new Label("componentTree", ""));
109        }
110
111        /**
112         * Converts a Throwable to a string.
113         * 
114         * @param throwable
115         *            The throwable
116         * @return The string
117         */
118        public String getErrorMessage(final Throwable throwable)
119        {
120                if (throwable != null)
121                {
122                        StringBuilder sb = new StringBuilder(256);
123
124                        // first print the last cause
125                        List<Throwable> al = convertToList(throwable);
126                        int length = al.size() - 1;
127                        Throwable cause = al.get(length);
128                        sb.append("Last cause: ").append(cause.getMessage()).append('\n');
129                        if (throwable instanceof WicketRuntimeException)
130                        {
131                                String msg = throwable.getMessage();
132                                if ((msg != null) && (msg.equals(cause.getMessage()) == false))
133                                {
134                                        if (throwable instanceof MarkupException)
135                                        {
136                                                MarkupStream stream = ((MarkupException)throwable).getMarkupStream();
137                                                if (stream != null)
138                                                {
139                                                        String text = "\n" + stream.toString();
140                                                        if (msg.endsWith(text))
141                                                        {
142                                                                msg = msg.substring(0, msg.length() - text.length());
143                                                        }
144                                                }
145                                        }
146
147                                        sb.append("WicketMessage: ");
148                                        sb.append(msg);
149                                        sb.append("\n\n");
150                                }
151                        }
152                        return sb.toString();
153                }
154                else
155                {
156                        return "[Unknown]";
157                }
158        }
159
160        /**
161         * Converts a Throwable to a string.
162         * 
163         * @param throwable
164         *            The throwable
165         * @return The string
166         */
167        public String getStackTrace(final Throwable throwable)
168        {
169                if (throwable != null)
170                {
171                        List<Throwable> al = convertToList(throwable);
172
173                        StringBuilder sb = new StringBuilder(256);
174
175                        // first print the last cause
176                        int length = al.size() - 1;
177                        Throwable cause = al.get(length);
178
179                        sb.append("Root cause:\n\n");
180                        outputThrowable(cause, sb, false);
181
182                        if (length > 0)
183                        {
184                                sb.append("\n\nComplete stack:\n\n");
185                                for (int i = 0; i < length; i++)
186                                {
187                                        outputThrowable(al.get(i), sb, true);
188                                        sb.append("\n");
189                                }
190                        }
191                        return sb.toString();
192                }
193                else
194                {
195                        return "<Null Throwable>";
196                }
197        }
198
199        /**
200         * @param throwable
201         * @return xxx
202         */
203        private List<Throwable> convertToList(final Throwable throwable)
204        {
205                List<Throwable> al = Generics.newArrayList();
206                Throwable cause = throwable;
207                al.add(cause);
208                while ((cause.getCause() != null) && (cause != cause.getCause()))
209                {
210                        cause = cause.getCause();
211                        al.add(cause);
212                }
213                return al;
214        }
215
216        /**
217         * Outputs the throwable and its stacktrace to the stringbuffer. If stopAtWicketSerlvet is true
218         * then the output will stop when the org.apache.wicket servlet is reached. sun.reflect.
219         * packages are filtered out.
220         * 
221         * @param cause
222         * @param sb
223         * @param stopAtWicketServlet
224         */
225        private void outputThrowable(Throwable cause, StringBuilder sb, boolean stopAtWicketServlet)
226        {
227                sb.append(cause);
228                sb.append("\n");
229                StackTraceElement[] trace = cause.getStackTrace();
230                for (int i = 0; i < trace.length; i++)
231                {
232                        String traceString = trace[i].toString();
233                        if (!(traceString.startsWith("sun.reflect.") && i > 1))
234                        {
235                                sb.append("     at ");
236                                sb.append(traceString);
237                                sb.append("\n");
238                                if (stopAtWicketServlet &&
239                                        (traceString.startsWith("org.apache.wicket.protocol.http.WicketServlet") || traceString.startsWith("org.apache.wicket.protocol.http.WicketFilter")))
240                                {
241                                        return;
242                                }
243                        }
244                }
245        }
246
247        @Override
248        protected void setHeaders(final WebResponse response)
249        {
250                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
251        }
252
253        /**
254         * Get access to the exception
255         * 
256         * @return The exception
257         */
258        public Throwable getThrowable()
259        {
260                return throwable;
261        }
262}