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.hivemind.servlet;
016
017import java.io.IOException;
018import java.util.Locale;
019
020import javax.servlet.Filter;
021import javax.servlet.FilterChain;
022import javax.servlet.FilterConfig;
023import javax.servlet.ServletContext;
024import javax.servlet.ServletException;
025import javax.servlet.ServletRequest;
026import javax.servlet.ServletResponse;
027import javax.servlet.http.HttpServletRequest;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.apache.hivemind.ClassResolver;
032import org.apache.hivemind.ModuleDescriptorProvider;
033import org.apache.hivemind.Registry;
034import org.apache.hivemind.impl.DefaultClassResolver;
035import org.apache.hivemind.impl.RegistryBuilder;
036import org.apache.hivemind.impl.XmlModuleDescriptorProvider;
037import org.apache.hivemind.util.ContextResource;
038
039/**
040 * Servlet filter that constructs the Registry at startup. It ensures that each request is properly
041 * terminated with a call to
042 * {@link org.apache.hivemind.service.ThreadEventNotifier#fireThreadCleanup()}. It also makes the
043 * Registry available during the request by storing it as a request attribute.
044 * 
045 * @author Howard Lewis Ship
046 */
047public class HiveMindFilter implements Filter
048{
049    private static final Log LOG = LogFactory.getLog(HiveMindFilter.class);
050
051    /**
052     * Request attribute key that stores the Registry.
053     */
054
055    static final String REQUEST_KEY = "org.apache.hivemind.RequestRegistry";
056
057    static final String REBUILD_REQUEST_KEY = "org.apache.hivemind.RebuildRegistry";
058
059    /** @since 1.1 */
060    static final String HIVE_MODULE_XML = "/WEB-INF/hivemodule.xml";
061
062    private FilterConfig _filterConfig;
063
064    private Registry _registry;
065
066    /**
067     * Constructs a {@link Registry} and stores it into the <code>ServletContext</code>. Any
068     * exception throws is logged.
069     */
070    public void init(FilterConfig config) throws ServletException
071    {
072        _filterConfig = config;
073
074        initializeRegistry();
075
076    }
077
078    private void initializeRegistry()
079    {
080        long startTime = System.currentTimeMillis();
081
082        LOG.info(ServletMessages.filterInit());
083
084        try
085        {
086            _registry = constructRegistry(_filterConfig);
087
088            LOG.info(ServletMessages.constructedRegistry(_registry, System.currentTimeMillis()
089                    - startTime));
090        }
091        catch (Exception ex)
092        {
093            LOG.error(ex.getMessage(), ex);
094        }
095    }
096
097    /**
098     * Invoked from {@link #init(FilterConfig)} to actually construct the Registry. Subclasses may
099     * override if they have specific initialization needs, or have nonstandard rules for finding
100     * HiveMind module deployment descriptors.
101     */
102    protected Registry constructRegistry(FilterConfig config)
103    {
104        RegistryBuilder builder = new RegistryBuilder();
105
106        ClassResolver resolver = new DefaultClassResolver();
107
108        builder.addModuleDescriptorProvider(getModuleDescriptorProvider(resolver));
109
110        addWebInfDescriptor(config.getServletContext(), resolver, builder);
111
112        return builder.constructRegistry(getRegistryLocale());
113    }
114
115    /**
116     * Invoked from {@link #constructRegistry(FilterConfig)} to add WEB-INF/hivemodule.xml to
117     * the registry, if it exists.
118     * 
119     * @since 1.1
120     */
121
122    protected void addWebInfDescriptor(ServletContext context, ClassResolver resolver,
123            RegistryBuilder builder)
124    {
125        ContextResource r = new ContextResource(context, HIVE_MODULE_XML);
126
127        if (r.getResourceURL() != null)
128        {
129            ModuleDescriptorProvider provider = new XmlModuleDescriptorProvider(resolver, r);
130
131            builder.addModuleDescriptorProvider(provider);
132        }
133    }
134
135    /**
136     * Returns the default Locale. Subclasses may override to select a particular locale for the
137     * Registry.
138     */
139    protected Locale getRegistryLocale()
140    {
141        return Locale.getDefault();
142    }
143
144    /**
145     * Returns the {@link ModuleDescriptorProvider} to be used to construct the Registry. This
146     * implementation returns the default {@link XmlModuleDescriptorProvider}. May be overridden by
147     * subclasses.
148     * 
149     * @since 1.1
150     */
151    protected ModuleDescriptorProvider getModuleDescriptorProvider(ClassResolver resolver)
152    {
153        return new XmlModuleDescriptorProvider(resolver);
154    }
155
156    /**
157     * Passes the request to the filter chain, but then invokes {@link Registry#cleanupThread()}
158     * &nbsp; (from a finally block).
159     */
160    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
161            throws IOException, ServletException
162    {
163        try
164        {
165            // I believe the _registry will only be null in a couple of test situations.
166
167            if (_registry != null)
168                _registry.setupThread();
169
170            request.setAttribute(REQUEST_KEY, _registry);
171
172            chain.doFilter(request, response);
173        }
174        finally
175        {
176            cleanupThread();
177
178            checkRegistryRebuild(request);
179        }
180    }
181
182    private synchronized void checkRegistryRebuild(ServletRequest request)
183    {
184        if (request.getAttribute(REBUILD_REQUEST_KEY) == null)
185            return;
186
187        Registry oldRegistry = _registry;
188
189        // Replace the old Registry with a new one. All other threads, but this
190        // one, will begin using the new Registry. Hopefully, we didn't get
191        // rebuild requests on multiple threads.
192
193        initializeRegistry();
194
195        // Shutdown the old Registry. Perhaps we should sleep for a moment, first,
196        // to help ensure that other threads have "cleared out". If not, we'll see some
197        // instability at the instant we shutdown (i.e., all the proxies will get disabled).
198        // Alternately, we should create a WeakReference based monitor that shuts down the
199        // old registry when it is no longer used by any other threads. For the moment,
200        // this functionality is limited to development-time only (not production), so it isn't
201        // urgent.
202
203        oldRegistry.shutdown();
204    }
205
206    /**
207     * Cleanup the thread, ignoring any exceptions that may be thrown.
208     */
209    private void cleanupThread()
210    {
211        try
212        {
213            _registry.cleanupThread();
214        }
215        catch (Exception ex)
216        {
217            LOG.error(ServletMessages.filterCleanupError(ex), ex);
218        }
219    }
220
221    /**
222     * Invokes {@link Registry#shutdown()}.
223     */
224    public void destroy()
225    {
226        if (_registry != null)
227            _registry.shutdown();
228
229        _filterConfig = null;
230    }
231
232    /**
233     * Returns the {@link Registry} that was stored as a request attribute inside method
234     * {@link #doFilter(ServletRequest, ServletResponse, FilterChain)}.
235     */
236    public static Registry getRegistry(HttpServletRequest request)
237    {
238        return (Registry) request.getAttribute(REQUEST_KEY);
239    }
240
241    /**
242     * Sets a flag in the request that will cause the current Registry to be shutdown and replaced
243     * with a new Registry (at the end of the current request).
244     */
245    public static void rebuildRegistry(HttpServletRequest request)
246    {
247        request.setAttribute(REBUILD_REQUEST_KEY, Boolean.TRUE);
248    }
249
250}