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
015package org.apache.tapestry;
016
017import java.io.IOException;
018import java.util.Locale;
019
020import javax.servlet.ServletConfig;
021import javax.servlet.ServletContext;
022import javax.servlet.ServletException;
023import javax.servlet.http.HttpServlet;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.apache.hivemind.ClassResolver;
030import org.apache.hivemind.ErrorHandler;
031import org.apache.hivemind.Registry;
032import org.apache.hivemind.Resource;
033import org.apache.hivemind.impl.DefaultClassResolver;
034import org.apache.hivemind.impl.RegistryBuilder;
035import org.apache.hivemind.impl.StrictErrorHandler;
036import org.apache.hivemind.impl.XmlModuleDescriptorProvider;
037import org.apache.hivemind.util.ContextResource;
038import org.apache.tapestry.services.ApplicationInitializer;
039import org.apache.tapestry.services.ServletRequestServicer;
040import 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
054public 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}