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