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}