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