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;
016    
017    import java.io.IOException;
018    import java.util.Locale;
019    
020    import javax.servlet.ServletConfig;
021    import javax.servlet.ServletContext;
022    import javax.servlet.ServletException;
023    import javax.servlet.http.HttpServlet;
024    import javax.servlet.http.HttpServletRequest;
025    import javax.servlet.http.HttpServletResponse;
026    
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    import org.apache.hivemind.ClassResolver;
030    import org.apache.hivemind.ErrorHandler;
031    import org.apache.hivemind.Registry;
032    import org.apache.hivemind.Resource;
033    import org.apache.hivemind.impl.DefaultClassResolver;
034    import org.apache.hivemind.impl.RegistryBuilder;
035    import org.apache.hivemind.impl.StrictErrorHandler;
036    import org.apache.hivemind.impl.XmlModuleDescriptorProvider;
037    import org.apache.hivemind.util.ContextResource;
038    import org.apache.tapestry.services.ApplicationInitializer;
039    import org.apache.tapestry.services.ServletRequestServicer;
040    import org.apache.tapestry.util.exception.ExceptionAnalyzer;
041    
042    /**
043     * Links a servlet container with a Tapestry application. The servlet has some responsibilities
044     * related to bootstrapping the application (in terms of logging, reading the
045     * {@link ApplicationSpecification specification}, etc.). It is also responsible for creating or
046     * locating the {@link IEngine}and delegating incoming requests to it.
047     * <p>
048     * The servlet init parameter <code>org.apache.tapestry.specification-path</code> should be set to
049     * the complete resource path (within the classpath) to the application specification, i.e.,
050     * <code>/com/foo/bar/MyApp.application</code>.
051     * <p>
052     * In some servlet containers (notably <a href="www.bea.com"/>WebLogic </a>) it is necessary to
053     * invoke {@link HttpSession#setAttribute(String,Object)}in order to force a persistent value to be
054     * replicated to the other servers in the cluster. Tapestry applications usually only have a single
055     * persistent value, the {@link IEngine engine}. For persistence to work in such an environment,
056     * the JVM system property <code>org.apache.tapestry.store-engine</code> must be set to
057     * <code>true</code>. This will force the application servlet to restore the engine into the
058     * {@link HttpSession}at the end of each request cycle.
059     * <p>
060     * As of release 1.0.1, it is no longer necessary for a {@link HttpSession}to be created on the
061     * first request cycle. Instead, the HttpSession is created as needed by the {@link IEngine}...
062     * that is, when a visit object is created, or when persistent page state is required. Otherwise,
063     * for sessionless requests, an {@link IEngine}from a {@link Pool}is used. Additional work must be
064     * done so that the {@link IEngine}can change locale <em>without</em> forcing the creation of a
065     * session; this involves the servlet and the engine storing locale information in a {@link Cookie}.
066     * <p>
067     * As of release 4.0, this servlet will also create a HiveMind Registry and manage it.
068     * 
069     * @author Howard Lewis Ship
070     */
071    
072    public class ApplicationServlet extends HttpServlet
073    {
074        private static final long serialVersionUID = -8046042689991538059L;
075    
076            /**
077         * Prefix used to store the HiveMind Registry into the ServletContext. This string is suffixed
078         * with the servlet name (in case multiple Tapestry applications are executing within a single
079         * web application).
080         * 
081         * @since 4.0
082         */
083    
084        private static final String REGISTRY_KEY_PREFIX = "org.apache.tapestry.Registry:";
085    
086        private static final Log LOG = LogFactory.getLog(ApplicationServlet.class);
087    
088        /**
089         * Invokes {@link #doService(HttpServletRequest, HttpServletResponse)}.
090         * 
091         * @since 1.0.6
092         */
093    
094        public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException,
095                ServletException
096        {
097            doService(request, response);
098        }
099    
100        /**
101         * @since 2.3
102         */
103    
104        private ClassResolver _resolver;
105    
106        /**
107         * The key used to store the registry into the ServletContext.
108         * 
109         * @since 4.0
110         */
111    
112        private String _registryKey;
113    
114        /**
115         * @since 4.0
116         */
117    
118        private Registry _registry;
119    
120        /**
121         * @since 4.0
122         */
123        private ServletRequestServicer _requestServicer;
124    
125        /**
126         * Handles the GET and POST requests. Performs the following:
127         * <ul>
128         * <li>Construct a {@link RequestContext}
129         * <li>Invoke {@link #getEngine(RequestContext)}to get or create the {@link IEngine}
130         * <li>Invoke {@link IEngine#service(RequestContext)}on the application
131         * </ul>
132         */
133    
134        protected void doService(HttpServletRequest request, HttpServletResponse response)
135                throws IOException, ServletException
136        {
137            try
138            {
139                _registry.setupThread();
140    
141                _requestServicer.service(request, response);
142            }
143            catch (ServletException ex)
144            {
145                log("ServletException", ex);
146    
147                show(ex);
148    
149                // Rethrow it.
150    
151                throw ex;
152            }
153            catch (IOException ex)
154            {
155                log("IOException", ex);
156    
157                show(ex);
158    
159                // Rethrow it.
160    
161                throw ex;
162            }
163            finally
164            {
165                _registry.cleanupThread();
166            }
167        }
168    
169        protected void show(Exception ex)
170        {
171            System.err.println("\n\n**********************************************************\n\n");
172    
173            new ExceptionAnalyzer().reportException(ex, System.err);
174    
175            System.err.println("\n**********************************************************\n");
176    
177        }
178    
179        /**
180         * Invokes {@link #doService(HttpServletRequest, HttpServletResponse)}.
181         */
182    
183        public void doPost(HttpServletRequest request, HttpServletResponse response)
184                throws IOException, ServletException
185        {
186            doService(request, response);
187        }
188    
189        /**
190         * Reads the application specification when the servlet is first initialized. All
191         * {@link IEngine engine instances}will have access to the specification via the servlet.
192         * 
193         * @see #constructApplicationSpecification()
194         * @see #createResourceResolver()
195         */
196    
197        public void init(ServletConfig config) throws ServletException
198        {
199            String name = config.getServletName();
200    
201            _registryKey = REGISTRY_KEY_PREFIX + name;
202    
203            long startTime = System.currentTimeMillis();
204            long elapsedToRegistry = 0;
205    
206            super.init(config);
207    
208            _resolver = createClassResolver();
209    
210            try
211            {
212                _registry = constructRegistry(config);
213    
214                elapsedToRegistry = System.currentTimeMillis() - startTime;
215    
216                initializeApplication();
217    
218                config.getServletContext().setAttribute(_registryKey, _registry);
219            }
220            catch (Exception ex)
221            {
222                show(ex);
223    
224                throw new ServletException(TapestryMessages.servletInitFailure(ex), ex);
225            }
226    
227            long elapsedOverall = System.currentTimeMillis() - startTime;
228    
229            LOG.info(TapestryMessages.servletInit(name, elapsedToRegistry, elapsedOverall));
230        }
231    
232        /**
233         * Invoked from {@link #init(ServletConfig)}to create a resource resolver for the servlet
234         * (which will utlimately be shared and used through the application).
235         * <p>
236         * This implementation constructs a {@link DefaultResourceResolver}, subclasses may provide a
237         * different implementation.
238         * 
239         * @see #getResourceResolver()
240         * @since 2.3
241         */
242    
243        protected ClassResolver createClassResolver()
244        {
245            return new DefaultClassResolver();
246        }
247    
248        /**
249         * Invoked from {@link #init(ServletConfig)}to construct the Registry to be used by the
250         * application.
251         * <p>
252         * This looks in the standard places (on the classpath), but also in the WEB-INF/name and
253         * WEB-INF folders (where name is the name of the servlet).
254         * 
255         * @since 4.0
256         */
257        protected Registry constructRegistry(ServletConfig config)
258        {
259            ErrorHandler errorHandler = constructErrorHandler(config);
260    
261            RegistryBuilder builder = new RegistryBuilder(errorHandler);
262    
263            builder.addModuleDescriptorProvider(new XmlModuleDescriptorProvider(_resolver));
264    
265            String name = config.getServletName();
266            ServletContext context = config.getServletContext();
267    
268            addModuleIfExists(builder, context, "/WEB-INF/" + name + "/hivemodule.xml");
269            addModuleIfExists(builder, context, "/WEB-INF/hivemodule.xml");
270    
271            return builder.constructRegistry(Locale.getDefault());
272        }
273    
274        /**
275         * Invoked by {@link #constructRegistry(ServletConfig)} to create and return an
276         * {@link ErrorHandler} instance to be used when constructing the Registry (and then to handle
277         * any runtime exceptions). This implementation returns a new instance of
278         * {@link org.apache.hivemind.impl.StrictErrorHandler}.
279         * 
280         * @since 4.0
281         */
282        protected ErrorHandler constructErrorHandler(ServletConfig config)
283        {
284            return new StrictErrorHandler();
285        }
286    
287        /**
288         * Looks for a file in the servlet context; if it exists, it is expected to be a HiveMind module
289         * descriptor, and is added to the builder.
290         * 
291         * @since 4.0
292         */
293    
294        protected void addModuleIfExists(RegistryBuilder builder, ServletContext context, String path)
295        {
296            Resource r = new ContextResource(context, path);
297    
298            if (r.getResourceURL() == null)
299                return;
300    
301            builder.addModuleDescriptorProvider(new XmlModuleDescriptorProvider(_resolver, r));
302        }
303    
304        /**
305         * Invoked from {@link #init(ServletConfig)}, after the registry has been constructed, to
306         * bootstrap the application via the <code>tapestry.MasterApplicationInitializer</code>
307         * service.
308         * 
309         * @since 4.0
310         */
311        protected void initializeApplication()
312        {
313            ApplicationInitializer ai = (ApplicationInitializer) _registry.getService(
314                    "tapestry.init.MasterInitializer",
315                    ApplicationInitializer.class);
316    
317            ai.initialize(this);
318    
319            _registry.cleanupThread();
320    
321            _requestServicer = (ServletRequestServicer) _registry.getService(
322                    "tapestry.request.ServletRequestServicer",
323                    ServletRequestServicer.class);
324        }
325    
326        /**
327         * Shuts down the registry (if it exists).
328         * 
329         * @since 4.0
330         */
331        public void destroy()
332        {
333            getServletContext().removeAttribute(_registryKey);
334    
335            if (_registry != null)
336            {
337                _registry.shutdown();
338                _registry = null;
339            }
340        }
341    }