001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.engine;
016
017 import org.apache.commons.logging.Log;
018 import org.apache.commons.logging.LogFactory;
019 import org.apache.hivemind.ApplicationRuntimeException;
020 import org.apache.hivemind.ClassResolver;
021 import org.apache.hivemind.util.Defense;
022 import org.apache.hivemind.util.ToStringBuilder;
023 import org.apache.tapestry.*;
024 import org.apache.tapestry.listener.ListenerMap;
025 import org.apache.tapestry.services.ComponentMessagesSource;
026 import org.apache.tapestry.services.DataSqueezer;
027 import org.apache.tapestry.services.Infrastructure;
028 import org.apache.tapestry.services.TemplateSource;
029 import org.apache.tapestry.spec.IApplicationSpecification;
030 import org.apache.tapestry.web.WebRequest;
031 import org.apache.tapestry.web.WebResponse;
032
033 import javax.servlet.ServletContext;
034 import javax.servlet.ServletException;
035 import java.io.IOException;
036 import java.util.ArrayList;
037 import java.util.List;
038 import java.util.Locale;
039
040 /**
041 * Basis for building real Tapestry applications. Immediate subclasses provide different strategies
042 * for managing page state and other resources between request cycles.
043 * <p>
044 * Note: much of this description is <em>in transition</em> as part of Tapestry 4.0. All ad-hoc
045 * singletons and such are being replaced with HiveMind services.
046 * <p>
047 * Uses a shared instance of {@link TemplateSource},{@link ISpecificationSource},
048 * {@link IScriptSource}and {@link ComponentMessagesSource}stored as attributes of the
049 * {@link ServletContext}(they will be shared by all sessions).
050 * <p>
051 * An engine is designed to be very lightweight. Particularily, it should <b>never </b> hold
052 * references to any {@link IPage}or {@link org.apache.tapestry.IComponent}objects. The entire
053 * system is based upon being able to quickly rebuild the state of any page(s).
054 * <p>
055 * Where possible, instance variables should be transient.
056 * <p>
057 * In practice, a subclass (usually {@link BaseEngine}) is used without subclassing. Instead, a
058 * visit object is specified. To facilitate this, the application specification may include a
059 * property, <code>org.apache.tapestry.visit-class</code> which is the class name to instantiate
060 * when a visit object is first needed.
061 * <p>
062 * Some of the classes' behavior is controlled by JVM system properties (typically only used during
063 * development): <table border=1>
064 * <tr>
065 * <th>Property</th>
066 * <th>Description</th>
067 * </tr>
068 * <tr>
069 * <td>org.apache.tapestry.enable-reset-service</td>
070 * <td>If true, enabled an additional service, reset, that allow page, specification and template
071 * caches to be cleared on demand.</td>
072 * </tr>
073 * <tr>
074 * <td>org.apache.tapestry.disable-caching</td>
075 * <td>If true, then the page, specification, template and script caches will be cleared after each
076 * request. This slows things down, but ensures that the latest versions of such files are used.
077 * Care should be taken that the source directories for the files preceeds any versions of the files
078 * available in JARs or WARs.</td>
079 * </tr>
080 * </table>
081 *
082 * @author Howard Lewis Ship
083 */
084
085 public abstract class AbstractEngine implements IEngine
086 {
087 /**
088 * The name of the application specification property used to specify the class of the visit
089 * object.
090 */
091
092 public static final String VISIT_CLASS_PROPERTY_NAME = "org.apache.tapestry.visit-class";
093
094 private static final Log LOG = LogFactory.getLog(AbstractEngine.class);
095
096 /**
097 * The link to the world of HiveMind services.
098 *
099 * @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 _infrastructure.getExceptionPresenter().presentException(cycle, cause);
118 }
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 _infrastructure.getRequestExceptionReporter().reportRequestException(reportTitle, ex);
129 }
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 }
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 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 return _infrastructure.getServiceMap().getService(name);
161 }
162
163 /** @see Infrastructure#getApplicationSpecification() */
164
165 public IApplicationSpecification getSpecification()
166 {
167 return _infrastructure.getApplicationSpecification();
168 }
169
170 /** @see Infrastructure#getSpecificationSource() */
171
172 public ISpecificationSource getSpecificationSource()
173 {
174 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 IPage page = cycle.getPage(pageName);
187
188 cycle.activate(page);
189
190 renderResponse(cycle);
191 }
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 _infrastructure.getResponseRenderer().renderResponse(cycle);
202 }
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 IRequestCycle cycle = null;
212 IEngineService service = null;
213
214 if (_infrastructure == null)
215 _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 cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this);
223 }
224 catch (RuntimeException ex)
225 {
226 throw ex;
227 }
228 catch (Exception ex)
229 {
230 throw new IOException(ex.getMessage());
231 }
232
233 try
234 {
235 try
236 {
237 service = cycle.getService();
238
239 // Let the service handle the rest of the request.
240
241 service.service(cycle);
242 }
243 catch (PageRedirectException ex)
244 {
245 handlePageRedirectException(cycle, ex);
246 }
247 catch (RedirectException ex)
248 {
249 handleRedirectException(cycle, ex);
250 }
251 catch (StaleLinkException ex)
252 {
253 handleStaleLinkException(cycle, ex);
254 }
255 catch (StaleSessionException ex)
256 {
257 handleStaleSessionException(cycle, ex);
258 }
259 }
260 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 if (LOG.isDebugEnabled())
267 LOG.debug("Uncaught exception", ex);
268
269 activateExceptionPage(cycle, ex);
270 }
271 finally
272 {
273 try
274 {
275 cycle.cleanup();
276 _infrastructure.getApplicationStateManager().flush();
277 }
278 catch (Exception ex)
279 {
280 reportException(EngineMessages.exceptionDuringCleanup(ex), ex);
281 }
282 }
283 }
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 List pageNames = new ArrayList();
299
300 String pageName = exception.getTargetPageName();
301
302 while (true)
303 {
304 if (pageNames.contains(pageName))
305 {
306 pageNames.add(pageName);
307
308 throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames));
309 }
310
311 // Record that this page has been a target.
312
313 pageNames.add(pageName);
314
315 try
316 {
317 // Attempt to activate the new page.
318
319 cycle.activate(pageName);
320
321 break;
322 }
323 catch (PageRedirectException secondRedirectException)
324 {
325 pageName = secondRedirectException.getTargetPageName();
326 }
327 }
328
329 renderResponse(cycle);
330 }
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 _infrastructure.getStaleLinkExceptionPresenter().presentStaleLinkException(cycle, exception);
357 }
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 _infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(cycle, exception);
376 }
377
378 /**
379 * Changes the locale for the engine.
380 */
381
382 public void setLocale(Locale value)
383 {
384 Defense.notNull(value, "locale");
385
386 _locale = value;
387
388 // The locale may be set before the engine is initialized with the Infrastructure.
389
390 if (_infrastructure != null)
391 _infrastructure.setLocale(value);
392 }
393
394 /**
395 * @see Infrastructure#getClassResolver()
396 */
397
398 public ClassResolver getClassResolver()
399 {
400 return _infrastructure.getClassResolver();
401 }
402
403 /**
404 * {@inheritDoc}
405 */
406
407 public String toString()
408 {
409 ToStringBuilder builder = new ToStringBuilder(this);
410
411 builder.append("locale", _locale);
412
413 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 return _infrastructure.getApplicationStateManager().get("visit");
428 }
429
430 public void setVisit(Object visit)
431 {
432 _infrastructure.getApplicationStateManager().store("visit", visit);
433 }
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 return getVisit();
444 }
445
446 public boolean getHasVisit()
447 {
448 return _infrastructure.getApplicationStateManager().exists("visit");
449 }
450
451 public IScriptSource getScriptSource()
452 {
453 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 if (_listeners == null)
465 _listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this);
466
467 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 String location = redirectException.getRedirectLocation();
482
483 if (LOG.isDebugEnabled())
484 LOG.debug("Redirecting to: " + location);
485
486 _infrastructure.getRequest().forward(location);
487 }
488
489 /**
490 * @see Infrastructure#getDataSqueezer()
491 */
492
493 public DataSqueezer getDataSqueezer()
494 {
495 return _infrastructure.getDataSqueezer();
496 }
497
498 /** @since 2.3 */
499
500 public IPropertySource getPropertySource()
501 {
502 return _infrastructure.getApplicationPropertySource();
503 }
504
505 /** @since 4.0 */
506 public Infrastructure getInfrastructure()
507 {
508 return _infrastructure;
509 }
510
511 public String getOutputEncoding()
512 {
513 return _infrastructure.getOutputEncoding();
514 }
515 }