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