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    
015    package org.apache.tapestry.portlet;
016    
017    import java.io.CharArrayWriter;
018    import java.io.IOException;
019    import java.io.PrintWriter;
020    
021    import javax.portlet.ActionResponse;
022    
023    import org.apache.hivemind.ApplicationRuntimeException;
024    import org.apache.tapestry.IMarkupWriter;
025    import org.apache.tapestry.IRequestCycle;
026    import org.apache.tapestry.describe.RenderStrategy;
027    import org.apache.tapestry.error.ErrorMessages;
028    import org.apache.tapestry.error.ExceptionPresenter;
029    import org.apache.tapestry.error.RequestExceptionReporter;
030    import org.apache.tapestry.markup.MarkupWriterSource;
031    import org.apache.tapestry.services.ServiceConstants;
032    import org.apache.tapestry.util.ContentType;
033    import org.apache.tapestry.util.exception.ExceptionAnalyzer;
034    import org.apache.tapestry.util.exception.ExceptionDescription;
035    import org.apache.tapestry.util.exception.ExceptionProperty;
036    import org.apache.tapestry.web.WebRequest;
037    import 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     */
050    public 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    }