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