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