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