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