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