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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | Defense.notNull(value, "locale"); |
385 | ||
386 | 0 | _locale = value; |
387 | ||
388 | // The locale may be set before the engine is initialized with the Infrastructure. | |
389 | ||
390 | 0 | if (_infrastructure != null) |
391 | 0 | _infrastructure.setLocale(value); |
392 | 0 | } |
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 | } |