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