    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}