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