001// Copyright 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry.portlet;
016
017import java.io.CharArrayWriter;
018import java.io.IOException;
019import java.io.PrintWriter;
020
021import javax.portlet.ActionResponse;
022
023import org.apache.hivemind.ApplicationRuntimeException;
024import org.apache.tapestry.IMarkupWriter;
025import org.apache.tapestry.IRequestCycle;
026import org.apache.tapestry.describe.RenderStrategy;
027import org.apache.tapestry.error.ErrorMessages;
028import org.apache.tapestry.error.ExceptionPresenter;
029import org.apache.tapestry.error.RequestExceptionReporter;
030import org.apache.tapestry.markup.MarkupWriterSource;
031import org.apache.tapestry.services.ServiceConstants;
032import org.apache.tapestry.util.ContentType;
033import org.apache.tapestry.util.exception.ExceptionAnalyzer;
034import org.apache.tapestry.util.exception.ExceptionDescription;
035import org.apache.tapestry.util.exception.ExceptionProperty;
036import org.apache.tapestry.web.WebRequest;
037import org.apache.tapestry.web.WebResponse;
038
039/**
040 * Service used to present a runtime exception to the user. This is very tricky in the Portlet world
041 * because of the split between the action and render requests (much more likely to get an error
042 * during the action request than during the render request, but both are possible).
043 * <p>
044 * During an action request, this code will render the HTML markup for the exception into a buffer
045 * that is stored as persistent attribute in the portal session.
046 * 
047 * @author Howard M. Lewis Ship
048 * @since 4.0
049 */
050public class PortletExceptionPresenter implements ExceptionPresenter
051{
052    private PortletRequestGlobals _globals;
053
054    private RenderStrategy _renderStrategy;
055
056    private WebRequest _request;
057
058    private RequestExceptionReporter _requestExceptionReporter;
059
060    private WebResponse _response;
061
062    private MarkupWriterSource _markupWriterSource;
063
064    public void presentException(IRequestCycle cycle, Throwable cause)
065    {
066        try
067        {
068            if (_globals.isRenderRequest())
069                reportRenderRequestException(cycle, cause);
070            else
071                reportActionRequestException(cycle, cause);
072        }
073        catch (Exception ex)
074        {
075            // Worst case scenario. The exception page itself is broken, leaving
076            // us with no option but to write the cause to the output.
077
078            // Also, write the exception thrown when redendering the exception
079            // page, so that can get fixed as well.
080
081            _requestExceptionReporter.reportRequestException(PortletMessages
082                    .errorReportingException(ex), ex);
083
084            // And throw the exception.
085
086            throw new ApplicationRuntimeException(ex.getMessage(), ex);
087        }
088
089        _requestExceptionReporter.reportRequestException(ErrorMessages
090                .unableToProcessClientRequest(cause), cause);
091    }
092
093    private void reportActionRequestException(IRequestCycle cycle, Throwable cause)
094    {
095        CharArrayWriter caw = new CharArrayWriter();
096        PrintWriter pw = new PrintWriter(caw);
097
098        IMarkupWriter writer = _markupWriterSource
099                .newMarkupWriter(pw, new ContentType("text/html"));
100
101        writeException(writer, cycle, cause);
102
103        writer.close();
104
105        String markup = caw.toString();
106
107        _request.getSession(true).setAttribute(
108                PortletConstants.PORTLET_EXCEPTION_MARKUP_ATTRIBUTE,
109                markup);
110
111        ActionResponse response = _globals.getActionResponse();
112
113        response.setRenderParameter(ServiceConstants.SERVICE, PortletConstants.EXCEPTION_SERVICE);
114    }
115
116    private void reportRenderRequestException(IRequestCycle cycle, Throwable cause)
117            throws IOException
118    {
119        PrintWriter pw = _response.getPrintWriter(new ContentType("text/html"));
120
121        IMarkupWriter writer = _markupWriterSource
122                .newMarkupWriter(pw, new ContentType("text/html"));
123
124        writeException(writer, cycle, cause);
125    }
126
127    public void setGlobals(PortletRequestGlobals globals)
128    {
129        _globals = globals;
130    }
131
132    public void setRenderStrategy(RenderStrategy renderStrategy)
133    {
134        _renderStrategy = renderStrategy;
135    }
136
137    public void setRequest(WebRequest request)
138    {
139        _request = request;
140    }
141
142    public void setRequestExceptionReporter(RequestExceptionReporter requestExceptionReporter)
143    {
144        _requestExceptionReporter = requestExceptionReporter;
145    }
146
147    public void setResponse(WebResponse response)
148    {
149        _response = response;
150    }
151
152    public void setMarkupWriterSource(MarkupWriterSource markupWriterSource)
153    {
154        _markupWriterSource = markupWriterSource;
155    }
156
157    private void writeException(IMarkupWriter writer, IRequestCycle cycle,
158            ExceptionDescription exception, boolean showStackTrace)
159    {
160        writer.begin("div");
161        writer.attribute("class", "portlet-section-header");
162        writer.print(exception.getExceptionClassName());
163        writer.end();
164        writer.println();
165
166        writer.begin("div");
167        writer.attribute("class", "portlet-msg-error");
168        writer.print(exception.getMessage());
169        writer.end();
170        writer.println();
171
172        ExceptionProperty[] properties = exception.getProperties();
173
174        if (properties.length > 0)
175        {
176
177            writer.begin("table");
178            writer.attribute("class", "portlet-section-subheader");
179
180            for (int i = 0; i < properties.length; i++)
181            {
182                writer.begin("tr");
183
184                writer.attribute("class", i % 2 == 0 ? "portlet-section-body"
185                        : "portlet-section-alternate");
186
187                writer.begin("th");
188                writer.print(properties[i].getName());
189                writer.end();
190                writer.println();
191
192                writer.begin("td");
193
194                _renderStrategy.renderObject(properties[i].getValue(), writer, cycle);
195                writer.end("tr");
196                writer.println();
197            }
198
199            writer.end();
200            writer.println();
201        }
202
203        if (!showStackTrace)
204            return;
205
206        writer.begin("ul");
207
208        String[] trace = exception.getStackTrace();
209
210        for (int i = 0; i < trace.length; i++)
211        {
212            writer.begin("li");
213            writer.print(trace[i]);
214            writer.end();
215            writer.println();
216        }
217
218        writer.end();
219        writer.println();
220
221    }
222
223    private void writeException(IMarkupWriter writer, IRequestCycle cycle, Throwable cause)
224    {
225        ExceptionDescription[] exceptions = new ExceptionAnalyzer().analyze(cause);
226
227        for (int i = 0; i < exceptions.length; i++)
228            writeException(writer, cycle, exceptions[i], i + 1 == exceptions.length);
229    }
230
231}