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