001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005//     http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.corelib.pages;
014
015import org.apache.tapestry5.ComponentResources;
016import org.apache.tapestry5.EventContext;
017import org.apache.tapestry5.SymbolConstants;
018import org.apache.tapestry5.alerts.AlertManager;
019import org.apache.tapestry5.annotations.ContentType;
020import org.apache.tapestry5.annotations.Import;
021import org.apache.tapestry5.annotations.Property;
022import org.apache.tapestry5.annotations.UnknownActivationContextCheck;
023import org.apache.tapestry5.beanmodel.services.*;
024import org.apache.tapestry5.commons.util.CollectionFactory;
025import org.apache.tapestry5.corelib.base.AbstractInternalPage;
026import org.apache.tapestry5.func.F;
027import org.apache.tapestry5.func.Mapper;
028import org.apache.tapestry5.http.Link;
029import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
030import org.apache.tapestry5.http.services.BaseURLSource;
031import org.apache.tapestry5.http.services.RequestGlobals;
032import org.apache.tapestry5.http.services.Session;
033import org.apache.tapestry5.internal.InternalConstants;
034import org.apache.tapestry5.internal.TapestryInternalUtils;
035import org.apache.tapestry5.internal.services.PageActivationContextCollector;
036import org.apache.tapestry5.internal.services.ReloadHelper;
037import org.apache.tapestry5.ioc.annotations.Inject;
038import org.apache.tapestry5.ioc.annotations.Symbol;
039import org.apache.tapestry5.ioc.internal.util.InternalUtils;
040import org.apache.tapestry5.services.ExceptionReporter;
041import org.apache.tapestry5.services.PageRenderLinkSource;
042import org.apache.tapestry5.services.URLEncoder;
043
044import java.net.MalformedURLException;
045import java.net.URL;
046import java.util.List;
047import java.util.regex.Pattern;
048
049/**
050 * Responsible for reporting runtime exceptions. This page is quite verbose and is usually overridden in a production
051 * application. When {@link org.apache.tapestry5.http.TapestryHttpSymbolConstants#PRODUCTION_MODE} is "true", it is very abbreviated.
052 *
053 * @see org.apache.tapestry5.corelib.components.ExceptionDisplay
054 */
055@UnknownActivationContextCheck(false)
056@ContentType("text/html")
057@Import(stylesheet = "ExceptionReport.css")
058public class ExceptionReport extends AbstractInternalPage implements ExceptionReporter
059{
060    private static final String PATH_SEPARATOR_PROPERTY = "path.separator";
061
062    // Match anything ending in .(something?)path.
063
064    private static final Pattern PATH_RECOGNIZER = Pattern.compile("\\..*path$");
065
066    @Property
067    private String attributeName;
068
069    @Inject
070    @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
071    @Property(write = false)
072    private boolean productionMode;
073
074    @Inject
075    @Symbol(SymbolConstants.TAPESTRY_VERSION)
076    @Property(write = false)
077    private String tapestryVersion;
078
079    @Inject
080    @Symbol(TapestryHttpSymbolConstants.APPLICATION_VERSION)
081    @Property(write = false)
082    private String applicationVersion;
083
084    @Property(write = false)
085    private Throwable rootException;
086
087    @Property
088    private String propertyName;
089
090    @Inject
091    private RequestGlobals requestGlobals;
092
093    @Inject
094    private AlertManager alertManager;
095
096    @Inject
097    private PageActivationContextCollector pageActivationContextCollector;
098
099    @Inject
100    private PageRenderLinkSource linkSource;
101
102    @Inject
103    private BaseURLSource baseURLSource;
104
105    @Inject
106    private ReloadHelper reloadHelper;
107
108    @Inject
109    private URLEncoder urlEncoder;
110
111    @Property
112    private String rootURL;
113
114    @Property
115    private ThreadInfo thread;
116
117    @Inject
118    private ComponentResources resources;
119
120    private String failurePage;
121
122    /**
123     * A link the user may press to perform an action (e.g., "Reload page").
124     */
125    public static class ActionLink
126    {
127        public final String uri, label;
128
129
130        public ActionLink(String uri, String label)
131        {
132            this.uri = uri;
133            this.label = label;
134        }
135    }
136
137    @Property
138    private ActionLink actionLink;
139
140    public class ThreadInfo implements Comparable<ThreadInfo>
141    {
142        public final String className, name, state, flags;
143
144        public final ThreadGroup group;
145
146        public ThreadInfo(String className, String name, String state, String flags, ThreadGroup group)
147        {
148            this.className = className;
149            this.name = name;
150            this.state = state;
151            this.flags = flags;
152            this.group = group;
153        }
154
155        @Override
156        public int compareTo(ThreadInfo o)
157        {
158            return name.compareTo(o.name);
159        }
160    }
161
162    private final String pathSeparator = System.getProperty(PATH_SEPARATOR_PROPERTY);
163
164    /**
165     * Returns true for normal, non-XHR requests. Links (to the failure page, or to root page) are only
166     * presented if showActions is true.
167     */
168    public boolean isShowActions()
169    {
170        return !request.isXHR();
171    }
172
173    /**
174     * Returns true in development mode; enables the "with reload" actions.
175     */
176    public boolean isShowReload()
177    {
178        return !productionMode;
179    }
180
181    public void reportException(Throwable exception)
182    {
183        rootException = exception;
184
185        rootURL = baseURLSource.getBaseURL(request.isSecure());
186
187        // Capture this now ... before the gears are shifted around to make ExceptionReport the active page.
188        failurePage = (request.getAttribute(InternalConstants.ACTIVE_PAGE_LOADED) == null)
189                ? null
190                : requestGlobals.getActivePageName();
191    }
192
193    private static void add(List<ActionLink> links, Link link, String format, Object... arguments)
194    {
195        String label = String.format(format, arguments);
196        links.add(new ActionLink(link.toURI(), label));
197    }
198
199    public List<ActionLink> getActionLinks()
200    {
201        List<ActionLink> links = CollectionFactory.newList();
202
203        if (failurePage != null)
204        {
205
206            try
207            {
208
209                Object[] pac = pageActivationContextCollector.collectPageActivationContext(failurePage);
210
211                add(links,
212                        linkSource.createPageRenderLinkWithContext(failurePage, pac),
213                        "Go to page <strong>%s</strong>", failurePage);
214
215                if (!productionMode)
216                {
217                    add(links,
218                            resources.createEventLink("reloadFirst", pac).addParameter("loadPage",
219                                    urlEncoder.encode(failurePage)),
220                            "Go to page <strong>%s</strong> (with reload)", failurePage);
221                }
222
223            } catch (Throwable t)
224            {
225                // Ignore.
226            }
227        }
228
229        links.add(new ActionLink(rootURL,
230                String.format("Go to <strong>%s</strong>", rootURL)));
231
232
233        if (!productionMode)
234        {
235            add(links,
236                    resources.createEventLink("reloadFirst"),
237                    "Go to <strong>%s</strong> (with reload)", rootURL);
238        }
239
240        return links;
241    }
242
243
244    Object onReloadFirst(EventContext reloadContext)
245    {
246        reloadHelper.forceReload();
247
248        return linkSource.createPageRenderLinkWithContext(urlEncoder.decode(request.getParameter("loadPage")), reloadContext);
249    }
250
251    Object onReloadRoot() throws MalformedURLException
252    {
253        reloadHelper.forceReload();
254
255        return new URL(baseURLSource.getBaseURL(request.isSecure()));
256    }
257
258
259    public boolean getHasSession()
260    {
261        return request.getSession(false) != null;
262    }
263
264    public Session getSession()
265    {
266        return request.getSession(false);
267    }
268
269    public Object getAttributeValue()
270    {
271        return getSession().getAttribute(attributeName);
272    }
273
274    /**
275     * Returns a <em>sorted</em> list of system property names.
276     */
277    public List<String> getSystemProperties()
278    {
279        return InternalUtils.sortedKeys(System.getProperties());
280    }
281
282    public String getPropertyValue()
283    {
284        return System.getProperty(propertyName);
285    }
286
287    public boolean isComplexProperty()
288    {
289        return PATH_RECOGNIZER.matcher(propertyName).find() && getPropertyValue().contains(pathSeparator);
290    }
291
292    public String[] getComplexPropertyValue()
293    {
294        // Neither : nor ; is a regexp character.
295
296        return getPropertyValue().split(pathSeparator);
297    }
298
299    public List<ThreadInfo> getThreads()
300    {
301        return F.flow(TapestryInternalUtils.getAllThreads()).map(new Mapper<Thread, ThreadInfo>()
302        {
303            @Override
304            public ThreadInfo map(Thread t)
305            {
306                List<String> flags = CollectionFactory.newList();
307
308                if (t.isDaemon())
309                {
310                    flags.add("daemon");
311                }
312                if (!t.isAlive())
313                {
314                    flags.add("NOT alive");
315                }
316                if (t.isInterrupted())
317                {
318                    flags.add("interrupted");
319                }
320
321                if (t.getPriority() != Thread.NORM_PRIORITY)
322                {
323                    flags.add("priority " + t.getPriority());
324                }
325
326                return new ThreadInfo(Thread.currentThread() == t ? "active-thread" : "",
327                        t.getName(),
328                        t.getState().name(),
329                        InternalUtils.join(flags),
330                        t.getThreadGroup());
331            }
332        }).sort().toList();
333    }
334}