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    }