    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}