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    public boolean isShowActions()
136    {
137        return failurePage != null && !request.isXHR();
138    }
139
140    public void reportException(Throwable exception)
141    {
142        rootException = exception;
143
144        failurePage = (request.getAttribute(InternalConstants.ACTIVE_PAGE_LOADED) == null)
145                ? null
146                : requestGlobals.getActivePageName();
147
148        rootURL = baseURLSource.getBaseURL(request.isSecure());
149    }
150
151    public Object[] getReloadContext()
152    {
153        return pageActivationContextCollector.collectPageActivationContext(failurePage);
154    }
155
156    Object onActionFromReloadFirst(EventContext reloadContext)
157    {
158        reloadHelper.forceReload();
159
160        return linkSource.createPageRenderLinkWithContext(urlEncoder.decode(request.getParameter("loadPage")), reloadContext);
161    }
162
163    Object onActionFromReloadRoot() throws MalformedURLException
164    {
165        reloadHelper.forceReload();
166
167        return new URL(baseURLSource.getBaseURL(request.isSecure()));
168    }
169
170
171    public boolean getHasSession()
172    {
173        return request.getSession(false) != null;
174    }
175
176    public Session getSession()
177    {
178        return request.getSession(false);
179    }
180
181    public Object getAttributeValue()
182    {
183        return getSession().getAttribute(attributeName);
184    }
185
186    /**
187     * Returns a <em>sorted</em> list of system property names.
188     */
189    public List<String> getSystemProperties()
190    {
191        return InternalUtils.sortedKeys(System.getProperties());
192    }
193
194    public String getPropertyValue()
195    {
196        return System.getProperty(propertyName);
197    }
198
199    public boolean isComplexProperty()
200    {
201        return PATH_RECOGNIZER.matcher(propertyName).find() && getPropertyValue().contains(pathSeparator);
202    }
203
204    public String[] getComplexPropertyValue()
205    {
206        // Neither : nor ; is a regexp character.
207
208        return getPropertyValue().split(pathSeparator);
209    }
210
211    public List<ThreadInfo> getThreads()
212    {
213        return F.flow(TapestryInternalUtils.getAllThreads()).map(new Mapper<Thread, ThreadInfo>()
214        {
215            @Override
216            public ThreadInfo map(Thread t)
217            {
218                List<String> flags = CollectionFactory.newList();
219
220                if (t.isDaemon())
221                {
222                    flags.add("daemon");
223                }
224                if (!t.isAlive())
225                {
226                    flags.add("NOT alive");
227                }
228                if (t.isInterrupted())
229                {
230                    flags.add("interrupted");
231                }
232
233                if (t.getPriority() != Thread.NORM_PRIORITY)
234                {
235                    flags.add("priority " + t.getPriority());
236                }
237
238                return new ThreadInfo(Thread.currentThread() == t ? "active-thread" : "",
239                        t.getName(),
240                        t.getState().name(),
241                        InternalUtils.join(flags),
242                        t.getThreadGroup());
243            }
244        }).sort().toList();
245    }
246}