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) throws IOException
184        {
185            IPage page = cycle.getPage(pageName);
186    
187            cycle.activate(page);
188    
189            renderResponse(cycle);
190        }
191    
192        /**
193         * Delegates to
194         * {@link org.apache.tapestry.services.ResponseRenderer#renderResponse(IRequestCycle)}.
195         */
196    
197        public void renderResponse(IRequestCycle cycle) throws IOException
198        {
199            _infrastructure.getResponseRenderer().renderResponse(cycle);
200        }
201    
202        /**
203         * Delegate method for the servlet. Services the request.
204         */
205    
206        public void service(WebRequest request, WebResponse response) throws IOException
207        {
208            IRequestCycle cycle = null;
209            IEngineService service = null;
210    
211            if (_infrastructure == null)
212                _infrastructure = (Infrastructure) request.getAttribute(Constants.INFRASTRUCTURE_KEY);
213    
214            // Create the request cycle; if this fails, there's not much that can be done ... everything
215            // else in Tapestry relies on the RequestCycle.
216    
217            try
218            {
219                cycle = _infrastructure.getRequestCycleFactory().newRequestCycle(this);
220            }
221            catch (RuntimeException ex)
222            {
223                throw ex;
224            }
225            catch (Exception ex)
226            {
227                throw new IOException(ex.getMessage());
228            }
229    
230            try
231            {
232                try
233                {
234                    service = cycle.getService();
235                    // Let the service handle the rest of the request.
236                    
237                    service.service(cycle);
238                    
239                    return;
240                }
241                catch (PageRedirectException ex)
242                {
243                    handlePageRedirectException(cycle, ex);
244                }
245                catch (RedirectException ex)
246                {
247                    handleRedirectException(cycle, ex);
248                }
249                catch (StaleLinkException ex)
250                {
251                    handleStaleLinkException(cycle, ex);
252                }
253                catch (StaleSessionException ex)
254                {
255                    handleStaleSessionException(cycle, ex);
256                }
257            }
258            catch (Exception ex)
259            {
260                // Attempt to switch to the exception page. However, this may itself
261                // fail for a number of reasons, in which case an ApplicationRuntimeException is
262                // thrown.
263    
264                if (LOG.isDebugEnabled())
265                    LOG.debug("Uncaught exception", ex);
266    
267                activateExceptionPage(cycle, ex);
268            }
269            finally
270            {
271                try
272                {
273                    cycle.cleanup();
274                    _infrastructure.getApplicationStateManager().flush();
275                }
276                catch (Exception ex)
277                {
278                    reportException(EngineMessages.exceptionDuringCleanup(ex), ex);
279                }
280            }
281        }
282        
283        /**
284         * Handles {@link PageRedirectException} which involves executing
285         * {@link IRequestCycle#activate(IPage)} on the target page (of the exception), until either a
286         * loop is found, or a page succesfully activates.
287         * <p>
288         * This should generally not be overriden in subclasses.
289         * 
290         * @since 3.0
291         */
292    
293        protected void handlePageRedirectException(IRequestCycle cycle, PageRedirectException exception)
294                throws IOException
295        {
296            List pageNames = new ArrayList();
297    
298            String pageName = exception.getTargetPageName();
299    
300            while (true)
301            {
302                if (pageNames.contains(pageName))
303                {
304                    pageNames.add(pageName);
305    
306                    throw new ApplicationRuntimeException(EngineMessages.validateCycle(pageNames));
307                }
308    
309                // Record that this page has been a target.
310    
311                pageNames.add(pageName);
312    
313                try
314                {
315                    // Attempt to activate the new page.
316    
317                    cycle.activate(pageName);
318    
319                    break;
320                }
321                catch (PageRedirectException secondRedirectException)
322                {
323                    pageName = secondRedirectException.getTargetPageName();
324                }
325            }
326    
327            renderResponse(cycle);
328        }
329    
330        /**
331         * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleLinkException} is
332         * thrown by the {@link IEngineService service}. This implementation sets the message property
333         * of the StaleLink page to the message provided in the exception, then invokes
334         * {@link #redirect(String, IRequestCycle, ApplicationRuntimeException)} to render the StaleLink
335         * page.
336         * <p>
337         * Subclasses may overide this method (without invoking this implementation). A better practice
338         * is to contribute an alternative implementation of
339         * {@link org.apache.tapestry.error.StaleLinkExceptionPresenter} to the
340         * tapestry.InfrastructureOverrides configuration point.
341         * <p>
342         * A common practice is to present an error message on the application's Home page. Alternately,
343         * the application may provide its own version of the StaleLink page, overriding the framework's
344         * implementation (probably a good idea, because the default page hints at "application errors"
345         * and isn't localized). The overriding StaleLink implementation must implement a message
346         * property of type String.
347         * 
348         * @since 0.2.10
349         */
350    
351        protected void handleStaleLinkException(IRequestCycle cycle, StaleLinkException exception)
352                throws IOException
353        {
354            _infrastructure.getStaleLinkExceptionPresenter().presentStaleLinkException(cycle, exception);
355        }
356    
357        /**
358         * Invoked by {@link #service(WebRequest, WebResponse)} if a {@link StaleSessionException} is
359         * thrown by the {@link IEngineService service}. This implementation uses the
360         * {@link org.apache.tapestry.error.StaleSessionExceptionPresenter} to render the StaleSession
361         * page.
362         * <p>
363         * Subclasses may overide this method (without invoking this implementation), but it is better
364         * to override the tapestry.error.StaleSessionExceptionReporter service instead (or contribute a
365         * replacement to the tapestry.InfrastructureOverrides configuration point).
366         * 
367         * @since 0.2.10
368         */
369    
370        protected void handleStaleSessionException(IRequestCycle cycle, StaleSessionException exception)
371                throws IOException
372        {
373            _infrastructure.getStaleSessionExceptionPresenter().presentStaleSessionException(cycle, exception);
374        }
375    
376        /**
377         * Changes the locale for the engine.
378         */
379    
380        public void setLocale(Locale value)
381        {
382            Defense.notNull(value, "locale");
383    
384            _locale = value;
385    
386            // The locale may be set before the engine is initialized with the Infrastructure.
387    
388            if (_infrastructure != null)
389                _infrastructure.setLocale(value);
390        }
391    
392        /**
393         * @see Infrastructure#getClassResolver()
394         */
395    
396        public ClassResolver getClassResolver()
397        {
398            return _infrastructure.getClassResolver();
399        }
400    
401        /**
402         * {@inheritDoc}
403         */
404        
405        public String toString()
406        {
407            ToStringBuilder builder = new ToStringBuilder(this);
408    
409            builder.append("locale", _locale);
410    
411            return builder.toString();
412        }
413    
414        /**
415         * Gets the visit object from the
416         * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, creating it if it does not
417         * already exist.
418         * <p>
419         * As of Tapestry 4.0, this will always create the visit object, possibly creating a new session
420         * in the process.
421         */
422    
423        public Object getVisit()
424        {
425            return _infrastructure.getApplicationStateManager().get("visit");
426        }
427    
428        public void setVisit(Object visit)
429        {
430            _infrastructure.getApplicationStateManager().store("visit", visit);
431        }
432    
433        /**
434         * Gets the visit object from the
435         * {@link org.apache.tapestry.engine.state.ApplicationStateManager}, which will create it as
436         * necessary.
437         */
438    
439        public Object getVisit(IRequestCycle cycle)
440        {
441            return getVisit();
442        }
443    
444        public boolean getHasVisit()
445        {
446            return _infrastructure.getApplicationStateManager().exists("visit");
447        }
448    
449        public IScriptSource getScriptSource()
450        {
451            return _infrastructure.getScriptSource();
452        }
453    
454        /**
455         * Allows subclasses to include listener methods easily.
456         * 
457         * @since 1.0.2
458         */
459    
460        public ListenerMap getListeners()
461        {
462            if (_listeners == null)
463                _listeners = _infrastructure.getListenerMapSource().getListenerMapForObject(this);
464    
465            return _listeners;
466        }
467    
468        /**
469         * Invoked when a {@link RedirectException} is thrown during the processing of a request.
470         * 
471         * @throws ApplicationRuntimeException
472         *             if an {@link IOException},{@link ServletException}is thrown by the redirect,
473         *             or if no {@link javax.servlet.RequestDispatcher} can be found for local resource.
474         * @since 2.2
475         */
476    
477        protected void handleRedirectException(IRequestCycle cycle, RedirectException redirectException)
478        {
479            String location = redirectException.getRedirectLocation();
480    
481            if (LOG.isDebugEnabled())
482                LOG.debug("Redirecting to: " + location);
483            
484            _infrastructure.getRequest().forward(location);
485        }
486    
487        /**
488         * @see Infrastructure#getDataSqueezer()
489         */
490    
491        public DataSqueezer getDataSqueezer()
492        {
493            return _infrastructure.getDataSqueezer();
494        }
495    
496        /** @since 2.3 */
497    
498        public IPropertySource getPropertySource()
499        {
500            return _infrastructure.getApplicationPropertySource();
501        }
502    
503        /** @since 4.0 */
504        public Infrastructure getInfrastructure()
505        {
506            return _infrastructure;
507        }
508    
509        public String getOutputEncoding()
510        {
511            return _infrastructure.getOutputEncoding();
512        }
513    }