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}