Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
AbstractEngine |
|
| 1.7037037037037037;1.704 |
1 | // Copyright 2004, 2005 The Apache Software Foundation |
|
2 | // |
|
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 | // you may not use this file except in compliance with the License. |
|
5 | // You may obtain a copy of the License at |
|
6 | // |
|
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
|
8 | // |
|
9 | // Unless required by applicable law or agreed to in writing, software |
|
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
|
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 | // See the License for the specific language governing permissions and |
|
13 | // limitations under the License. |
|
14 | ||
15 | package org.apache.tapestry.engine; |
|
16 | ||
17 | import org.apache.commons.logging.Log; |
|
18 | import org.apache.commons.logging.LogFactory; |
|
19 | import org.apache.hivemind.ApplicationRuntimeException; |
|
20 | import org.apache.hivemind.ClassResolver; |
|
21 | import org.apache.hivemind.util.Defense; |
|
22 | import org.apache.hivemind.util.ToStringBuilder; |
|
23 | import org.apache.tapestry.*; |
|
24 | import org.apache.tapestry.listener.ListenerMap; |
|
25 | import org.apache.tapestry.services.ComponentMessagesSource; |
|
26 | import org.apache.tapestry.services.DataSqueezer; |
|
27 | import org.apache.tapestry.services.Infrastructure; |
|
28 | import org.apache.tapestry.services.TemplateSource; |
|
29 | import org.apache.tapestry.spec.IApplicationSpecification; |
|
30 | import org.apache.tapestry.web.WebRequest; |
|
31 | import org.apache.tapestry.web.WebResponse; |
|
32 | ||
33 | import javax.servlet.ServletContext; |
|
34 | import javax.servlet.ServletException; |
|
35 | import java.io.IOException; |
|
36 | import java.util.ArrayList; |
|
37 | import java.util.List; |
|
38 | import java.util.Locale; |
|
39 | ||
40 | /** |
|
41 | * Basis for building real Tapestry applications. Immediate subclasses provide different strategies |
|
42 | * for managing page state and other resources between request cycles. |
|
43 | * <p> |
|
44 | * Note: much of this description is <em>in transition</em> as part of Tapestry 4.0. All ad-hoc |
|
45 | * singletons and such are being replaced with HiveMind services. |
|
46 | * <p> |
|
47 | * Uses a shared instance of {@link TemplateSource},{@link ISpecificationSource}, |
|
48 | * {@link IScriptSource}and {@link ComponentMessagesSource}stored as attributes of the |
|
49 | * {@link ServletContext}(they will be shared by all sessions). |
|
50 | * <p> |
|
51 | * An engine is designed to be very lightweight. Particularily, it should <b>never </b> hold |
|
52 | * references to any {@link IPage}or {@link org.apache.tapestry.IComponent}objects. The entire |
|
53 | * system is based upon being able to quickly rebuild the state of any page(s). |
|
54 | * <p> |
|
55 | * Where possible, instance variables should be transient. |
|
56 | * <p> |
|
57 | * In practice, a subclass (usually {@link BaseEngine}) is used without subclassing. Instead, a |
|
58 | * visit object is specified. To facilitate this, the application specification may include a |
|
59 | * property, <code>org.apache.tapestry.visit-class</code> which is the class name to instantiate |
|
60 | * when a visit object is first needed. |
|
61 | * <p> |
|
62 | * Some of the classes' behavior is controlled by JVM system properties (typically only used during |
|
63 | * development): <table border=1> |
|
64 | * <tr> |
|
65 | * <th>Property</th> |
|
66 | * <th>Description</th> |
|
67 | * </tr> |
|
68 | * <tr> |
|
69 | * <td>org.apache.tapestry.enable-reset-service</td> |
|
70 | * <td>If true, enabled an additional service, reset, that allow page, specification and template |
|
71 | * caches to be cleared on demand.</td> |
|
72 | * </tr> |
|
73 | * <tr> |
|
74 | * <td>org.apache.tapestry.disable-caching</td> |
|
75 | * <td>If true, then the page, specification, template and script caches will be cleared after each |
|
76 | * request. This slows things down, but ensures that the latest versions of such files are used. |
|
77 | * Care should be taken that the source directories for the files preceeds any versions of the files |
|
78 | * available in JARs or WARs.</td> |
|
79 | * </tr> |
|
80 | * </table> |
|
81 | * |
|
82 | * @author Howard Lewis Ship |
|
83 | */ |
|
84 | ||
85 | 3 | public abstract class AbstractEngine implements IEngine |
86 | { |
|
87 | /** |
|
88 | * The name of the application specification property used to specify the class of the visit |
|
89 | * object. |
|
90 | */ |
|
91 | ||
92 | public static final String VISIT_CLASS_PROPERTY_NAME = "org.apache.tapestry.visit-class"; |
|
93 | ||
94 | 2 | private static final Log LOG = LogFactory.getLog(AbstractEngine.class); |
95 | ||
96 | /** |
|
97 | * The link to the world of HiveMind services. |
|
98 | * |
|
99 | * @since 4.0 |
|
100 | */ |
|
101 | private Infrastructure _infrastructure; |
|
102 | ||
103 | private ListenerMap _listeners; |
|
104 | ||
105 | /** |
|
106 | * The curent locale for the engine, which may be changed at any time. |
|
107 | */ |
|
108 | ||
109 | private Locale _locale; |
|
110 | ||
111 | /** |
|
112 | * @see org.apache.tapestry.error.ExceptionPresenter |
|
113 | */ |
|
114 | ||
115 | protected void activateExceptionPage(IRequestCycle cycle, Throwable cause) |
|
116 | { |
|
117 | 0 | _infrastructure.getExceptionPresenter().presentException(cycle, cause); |
118 | 0 | } |
119 | ||
120 | /** |
|
121 | * Writes a detailed report of the exception to <code>System.err</code>. |
|
122 | * |
|
123 | * @see org.apache.tapestry.error.RequestExceptionReporter |
|
124 | */ |
|
125 | ||
126 | public void reportException(String reportTitle, Throwable ex) |
|
127 | { |
|
128 | 0 | _infrastructure.getRequestExceptionReporter().reportRequestException(reportTitle, ex); |
129 | 0 | } |
130 | ||
131 | /** |
|
132 | * Invoked at the end of the request cycle to release any resources specific to the request |
|
133 | * cycle. This implementation does nothing and may be overriden freely. |
|
134 | */ |
|
135 | ||
136 | protected void cleanupAfterRequest(IRequestCycle cycle) |
|
137 | { |
|
138 | ||
139 | 0 | } |
140 | ||
141 | /** |
|
142 | * Returns the locale for the engine. This is initially set by the {@link ApplicationServlet} |
|
143 | * but may be updated by the application. |
|
144 | */ |
|
145 | ||
146 | public Locale getLocale() |
|
147 | { |
|
148 | 2 | return _locale; |
149 | } |
|
150 | ||
151 | /** |
|
152 | * Returns a service with the given name. |
|
153 | * |
|
154 | * @see Infrastructure#getServiceMap() |
|
155 | * @see org.apache.tapestry.services.ServiceMap |
|
156 | */ |
|
157 | ||
158 | public IEngineService getService(String name) |
|
159 | { |
|
160 | 0 | return _infrastructure.getServiceMap().getService(name); |
161 | } |
|
162 | ||
163 | /** @see Infrastructure#getApplicationSpecification() */ |
|
164 | ||
165 | public IApplicationSpecification getSpecification() |
|
166 | { |
|
167 | 0 | return _infrastructure.getApplicationSpecification(); |
168 | } |
|
169 | ||
170 | /** @see Infrastructure#getSpecificationSource() */ |
|
171 | ||
172 | public ISpecificationSource getSpecificationSource() |
|
173 | { |
|
174 | 0 | return _infrastructure.getSpecificationSource(); |
175 | } |
|
176 | ||
177 | /** |
|
178 | * Invoked, typically, when an exception occurs while servicing the request. This method resets |
|
179 | * the output, sets the new page and renders it. |
|
180 | */ |
|
181 | ||
182 | protected void redirect(String pageName, IRequestCycle cycle, |
|
183 | ApplicationRuntimeException exception) |
|
184 | throws IOException |
|
185 | { |
|
186 | 0 | IPage page = cycle.getPage(pageName); |
187 | ||
188 | 0 | cycle.activate(page); |
189 | ||
190 | 0 | renderResponse(cycle); |
191 | 0 | } |
192 | ||
193 | /** |
|
194 | * Delegates to |
|
195 | * {@link org.apache.tapestry.services.ResponseRenderer#renderResponse(IRequestCycle)}. |
|
196 | */ |
|
197 | ||
198 | public void renderResponse(IRequestCycle cycle) |
|
199 | throws IOException |
|
200 | { |
|
201 | 0 | _infrastructure.getResponseRenderer().renderResponse(cycle); |
202 | 0 | } |
203 | ||
204 | /** |
|
205 | * Delegate method for the servlet. Services the request. |
|
206 | */ |
|
207 | ||
208 | public void service(WebRequest request, WebResponse response) |
|
209 | throws IOException |
|
210 | { |
|
211 | 0 | IRequestCycle cycle = null; |
212 | 0 | IEngineService service = null; |
213 | ||
214 | 0 | if (_infrastructure == null) |
215 | 0 | _infrastructure = (Infrastructure) request.getAttribute(Constants.INFRASTRUCTURE_KEY); |
216 | ||
217 | // Create the request cycle; if this fails, there's not much that can be done ... everything |
|
218 | // else in Tapestry relies on the RequestCycle. |
|
219 | ||
220 | try |
|
221 | { |
|
222 | 0 | cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this); |
223 | } |
|
224 | 0 | catch (RuntimeException ex) |
225 | { |
|
226 | 0 | throw ex; |
227 | } |
|
228 | 0 | catch (Exception ex) |
229 | { |
|
230 | 0 | throw new IOException(ex.getMessage()); |
231 | 0 | } |
232 | ||
233 | try |
|
234 | { |
|
235 | try |
|
236 | { |
|
237 | 0 | service = cycle.getService(); |
238 | ||
239 | // Let the service handle the rest of the request. |
|
240 | ||
241 | 0 | service.service(cycle); |
242 | } |
|
243 | 0 | catch (PageRedirectException ex) |
244 | { |
|
245 | 0 | handlePageRedirectException(cycle, ex); |
246 | } |
|
247 | 0 | catch (RedirectException ex) |
248 | { |
|
249 | 0 | handleRedirectException(cycle, ex); |
250 | } |
|
251 | 0 | catch (StaleLinkException ex) |
252 | { |
|
253 | 0 | handleStaleLinkException(cycle, ex); |
254 | } |
|
255 | 0 | catch (StaleSessionException ex) |
256 | { |
|
257 | 0 | handleStaleSessionException(cycle, ex); |
258 | 0 | } |
259 | } |
|
260 | 0 | catch (Exception ex) |
261 | { |
|
262 | // Attempt to switch to the exception page. However, this may itself |
|
263 | // fail for a number of reasons, in which case an ApplicationRuntimeException is |
|
264 | // thrown. |
|
265 | ||
266 | 0 | if (LOG.isDebugEnabled()) |
267 | 0 | LOG.debug("Uncaught exception", ex); |
268 | ||
269 | 0 | activateExceptionPage(cycle, ex); |
270 | } |
|
271 | finally |
|
272 | { |
|
273 | 0 | try |
274 | { |
|
275 | 0 | cycle.cleanup(); |
276 | 0 | _infrastructure.getApplicationStateManager().flush(); |
277 | } |
|
278 | 0 | catch (Exception ex) |
279 | { |
|
280 | 0 | reportException(EngineMessages.exceptionDuringCleanup(ex), ex); |
281 | 0 | } |
282 | 0 | } |
283 | 0 | } |
284 | ||
285 | /** |
|
286 | * Handles {@link PageRedirectException} which involves executing |
|
287 | * {@link IRequestCycle#activate(IPage)} on the target page (of the exception), until either a |
|
288 | * loop is found, or a page succesfully activates. |
|
289 | * <p> |
|
290 | * This should generally not be overriden in subclasses. |
|
291 | * |
|
292 | * @since 3.0 |
|
293 | */ |
|
294 | ||
295 | protected void handlePageRedirectException(IRequestCycle cycle, PageRedirectException exception) |
|
296 | throws IOException |
|
297 | { |
|
298 | 0 | List pageNames = new ArrayList(); |
299 | ||
300 | 0 | String pageName = exception.getTargetPageName(); |
301 | ||
302 | while (true) |
|
303 | { |
|
304 | 0 | if (pageNames.contains(pageName)) |
305 | { |
|
306 | 0 | pageNames.add(pageName); |
307 | ||
308 | 0 | throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames)); |
309 | } |
|
310 | ||
311 | // Record that this page has been a target. |
|
312 | ||
313 | 0 | pageNames.add(pageName); |
314 | ||
315 | try |
|
316 | { |
|
317 | // Attempt to activate the new page. |
|
318 | ||
319 | 0 | cycle.activate(pageName); |
320 | ||
321 | 0 | break; |
322 | } |
|
323 | 0 | catch (PageRedirectException secondRedirectException) |
324 | { |
|
325 | 0 | pageName = secondRedirectException.getTargetPageName(); |
326 | 0 | } |
327 | } |
|
328 | ||
329 | 0 | renderResponse(cycle); |
330 | 0 | } |
331 | ||
332 | /** |
|
333 | * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleLinkException} is |
|
334 | * thrown by the {@link IEngineService service}. This implementation sets the message property |
|
335 | * of the StaleLink page to the message provided in the exception, then invokes |
|
336 | * {@link #redirect(String, IRequestCycle, ApplicationRuntimeException)} to render the StaleLink |
|
337 | * page. |
|
338 | * <p> |
|
339 | * Subclasses may overide this method (without invoking this implementation). A better practice |
|
340 | * is to contribute an alternative implementation of |
|
341 | * {@link org.apache.tapestry.error.StaleLinkExceptionPresenter} to the |
|
342 | * tapestry.InfrastructureOverrides configuration point. |
|
343 | * <p> |
|
344 | * A common practice is to present an error message on the application's Home page. Alternately, |
|
345 | * the application may provide its own version of the StaleLink page, overriding the framework's |
|
346 | * implementation (probably a good idea, because the default page hints at "application errors" |
|
347 | * and isn't localized). The overriding StaleLink implementation must implement a message |
|
348 | * property of type String. |
|
349 | * |
|
350 | * @since 0.2.10 |
|
351 | */ |
|
352 | ||
353 | protected void handleStaleLinkException(IRequestCycle cycle, StaleLinkException exception) |
|
354 | throws IOException |
|
355 | { |
|
356 | 0 | _infrastructure.getStaleLinkExceptionPresenter().presentStaleLinkException(cycle, exception); |
357 | 0 | } |
358 | ||
359 | /** |
|
360 | * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleSessionException} is |
|
361 | * thrown by the {@link IEngineService service}. This implementation uses the |
|
362 | * {@link org.apache.tapestry.error.StaleSessionExceptionPresenter} to render the StaleSession |
|
363 | * page. |
|
364 | * <p> |
|
365 | * Subclasses may overide this method (without invoking this implementation), but it is better |
|
366 | * to override the tapestry.error.StaleSessionExceptionReporter service instead (or contribute a |
|
367 | * replacement to the tapestry.InfrastructureOverrides configuration point). |
|
368 | * |
|
369 | * @since 0.2.10 |
|
370 | */ |
|
371 | ||
372 | protected void handleStaleSessionException(IRequestCycle cycle, StaleSessionException exception) |
|
373 | throws IOException |
|
374 | { |
|
375 | 0 | _infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(cycle, exception); |
376 | 0 | } |
377 | ||
378 | /** |
|
379 | * Changes the locale for the engine. |
|
380 | */ |
|
381 | ||
382 | public void setLocale(Locale value) |
|
383 | { |
|
384 | 3 | Defense.notNull(value, "locale"); |
385 | ||
386 | 3 | _locale = value; |
387 | ||
388 | // The locale may be set before the engine is initialized with the Infrastructure. |
|
389 | ||
390 | 3 | if (_infrastructure != null) |
391 | 0 | _infrastructure.setLocale(value); |
392 | 3 | } |
393 | ||
394 | /** |
|
395 | * @see Infrastructure#getClassResolver() |
|
396 | */ |
|
397 | ||
398 | public ClassResolver getClassResolver() |
|
399 | { |
|
400 | 0 | return _infrastructure.getClassResolver(); |
401 | } |
|
402 | ||
403 | /** |
|
404 | * {@inheritDoc} |
|
405 | */ |
|
406 | ||
407 | public String toString() |
|
408 | { |
|
409 | 0 | ToStringBuilder builder = new ToStringBuilder(this); |
410 | ||
411 | 0 | builder.append("locale", _locale); |
412 | ||
413 | 0 | return builder.toString(); |
414 | } |
|
415 | ||
416 | /** |
|
417 | * Gets the visit object from the |
|
418 | * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, creating it if it does not |
|
419 | * already exist. |
|
420 | * <p> |
|
421 | * As of Tapestry 4.0, this will always create the visit object, possibly creating a new session |
|
422 | * in the process. |
|
423 | */ |
|
424 | ||
425 | public Object getVisit() |
|
426 | { |
|
427 | 0 | return _infrastructure.getApplicationStateManager().get("visit"); |
428 | } |
|
429 | ||
430 | public void setVisit(Object visit) |
|
431 | { |
|
432 | 0 | _infrastructure.getApplicationStateManager().store("visit", visit); |
433 | 0 | } |
434 | ||
435 | /** |
|
436 | * Gets the visit object from the |
|
437 | * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, which will create it as |
|
438 | * necessary. |
|
439 | */ |
|
440 | ||
441 | public Object getVisit(IRequestCycle cycle) |
|
442 | { |
|
443 | 0 | return getVisit(); |
444 | } |
|
445 | ||
446 | public boolean getHasVisit() |
|
447 | { |
|
448 | 0 | return _infrastructure.getApplicationStateManager().exists("visit"); |
449 | } |
|
450 | ||
451 | public IScriptSource getScriptSource() |
|
452 | { |
|
453 | 0 | return _infrastructure.getScriptSource(); |
454 | } |
|
455 | ||
456 | /** |
|
457 | * Allows subclasses to include listener methods easily. |
|
458 | * |
|
459 | * @since 1.0.2 |
|
460 | */ |
|
461 | ||
462 | public ListenerMap getListeners() |
|
463 | { |
|
464 | 0 | if (_listeners == null) |
465 | 0 | _listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this); |
466 | ||
467 | 0 | return _listeners; |
468 | } |
|
469 | ||
470 | /** |
|
471 | * Invoked when a {@link RedirectException} is thrown during the processing of a request. |
|
472 | * |
|
473 | * @throws ApplicationRuntimeException |
|
474 | * if an {@link IOException},{@link ServletException}is thrown by the redirect, |
|
475 | * or if no {@link javax.servlet.RequestDispatcher} can be found for local resource. |
|
476 | * @since 2.2 |
|
477 | */ |
|
478 | ||
479 | protected void handleRedirectException(IRequestCycle cycle, RedirectException redirectException) |
|
480 | { |
|
481 | 0 | String location = redirectException.getRedirectLocation(); |
482 | ||
483 | 0 | if (LOG.isDebugEnabled()) |
484 | 0 | LOG.debug("Redirecting to: " + location); |
485 | ||
486 | 0 | _infrastructure.getRequest().forward(location); |
487 | 0 | } |
488 | ||
489 | /** |
|
490 | * @see Infrastructure#getDataSqueezer() |
|
491 | */ |
|
492 | ||
493 | public DataSqueezer getDataSqueezer() |
|
494 | { |
|
495 | 0 | return _infrastructure.getDataSqueezer(); |
496 | } |
|
497 | ||
498 | /** @since 2.3 */ |
|
499 | ||
500 | public IPropertySource getPropertySource() |
|
501 | { |
|
502 | 0 | return _infrastructure.getApplicationPropertySource(); |
503 | } |
|
504 | ||
505 | /** @since 4.0 */ |
|
506 | public Infrastructure getInfrastructure() |
|
507 | { |
|
508 | 0 | return _infrastructure; |
509 | } |
|
510 | ||
511 | public String getOutputEncoding() |
|
512 | { |
|
513 | 0 | return _infrastructure.getOutputEncoding(); |
514 | } |
|
515 | } |