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