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