001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.web.servlet;
020
021import org.slf4j.Logger;
022import org.slf4j.LoggerFactory;
023
024import javax.servlet.FilterChain;
025import javax.servlet.ServletException;
026import javax.servlet.ServletRequest;
027import javax.servlet.ServletResponse;
028import java.io.IOException;
029
030/**
031 * A Servlet Filter that enables AOP-style "around" advice for a ServletRequest via
032 * {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) preHandle},
033 * {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) postHandle},
034 * and {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) afterCompletion}
035 * hooks.
036 *
037 * @since 0.9
038 */
039public abstract class AdviceFilter extends OncePerRequestFilter {
040
041    /**
042     * The static logger available to this class only
043     */
044    private static final Logger LOGGER = LoggerFactory.getLogger(AdviceFilter.class);
045
046    /**
047     * Returns {@code true} if the filter chain should be allowed to continue, {@code false} otherwise.
048     * It is called before the chain is actually consulted/executed.
049     * <p/>
050     * The default implementation returns {@code true} always and exists as a template method for subclasses.
051     *
052     * @param request  the incoming ServletRequest
053     * @param response the outgoing ServletResponse
054     * @return {@code true} if the filter chain should be allowed to continue, {@code false} otherwise.
055     * @throws Exception if there is any error.
056     */
057    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
058        return true;
059    }
060
061    /**
062     * Allows 'post' advice logic to be called, but only if no exception occurs during filter chain execution.  That
063     * is, if {@link #executeChain executeChain} throws an exception, this method will never be called.  Be aware of
064     * this when implementing logic.  Most resource 'cleanup' behavior is often done in the
065     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception)
066     *      afterCompletion(request,response,exception)}
067     * implementation, which is guaranteed to be called for every request, even when the chain processing throws
068     * an Exception.
069     * <p/>
070     * The default implementation does nothing (no-op) and exists as a template method for subclasses.
071     *
072     * @param request  the incoming ServletRequest
073     * @param response the outgoing ServletResponse
074     * @throws Exception if an error occurs.
075     */
076    @SuppressWarnings({"UnusedDeclaration"})
077    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
078    }
079
080    /**
081     * Called in all cases in a {@code finally} block even if {@link #preHandle preHandle} returns
082     * {@code false} or if an exception is thrown during filter chain processing.  Can be used for resource
083     * cleanup if so desired.
084     * <p/>
085     * The default implementation does nothing (no-op) and exists as a template method for subclasses.
086     *
087     * @param request   the incoming ServletRequest
088     * @param response  the outgoing ServletResponse
089     * @param exception any exception thrown during {@link #preHandle preHandle}, {@link #executeChain executeChain},
090     *                  or {@link #postHandle postHandle} execution, or {@code null} if no exception was thrown
091     *                  (i.e. the chain processed successfully).
092     * @throws Exception if an error occurs.
093     */
094    @SuppressWarnings({"UnusedDeclaration"})
095    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
096    }
097
098    /**
099     * Actually executes the specified filter chain by calling <code>chain.doFilter(request,response);</code>.
100     * <p/>
101     * Can be overridden by subclasses for custom logic.
102     *
103     * @param request  the incoming ServletRequest
104     * @param response the outgoing ServletResponse
105     * @param chain    the filter chain to execute
106     * @throws Exception if there is any error executing the chain.
107     */
108    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
109        chain.doFilter(request, response);
110    }
111
112    /**
113     * Actually implements the chain execution logic, utilizing
114     * {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) pre},
115     * {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) post}, and
116     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) after}
117     * advice hooks.
118     *
119     * @param request  the incoming ServletRequest
120     * @param response the outgoing ServletResponse
121     * @param chain    the filter chain to execute
122     * @throws ServletException if a servlet-related error occurs
123     * @throws IOException      if an IO error occurs
124     */
125    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
126            throws ServletException, IOException {
127
128        Exception exception = null;
129
130        try {
131
132            boolean continueChain = preHandle(request, response);
133            if (LOGGER.isTraceEnabled()) {
134                LOGGER.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
135            }
136
137            if (continueChain) {
138                executeChain(request, response, chain);
139            }
140
141            postHandle(request, response);
142            if (LOGGER.isTraceEnabled()) {
143                LOGGER.trace("Successfully invoked postHandle method");
144            }
145
146        } catch (Exception e) {
147            exception = e;
148        } finally {
149            cleanup(request, response, exception);
150        }
151    }
152
153    /**
154     * Executes cleanup logic in the {@code finally} code block in the
155     * {@link #doFilterInternal(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
156     *      doFilterInternal(request, response, filterChain)}
157     * implementation.
158     * <p/>
159     * This implementation specifically calls
160     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) afterCompletion}
161     * as well as handles any exceptions properly.
162     *
163     * @param request  the incoming {@code ServletRequest}
164     * @param response the outgoing {@code ServletResponse}
165     * @param existing any exception that might have occurred while executing the {@code FilterChain} or
166     *                 pre or post advice, or {@code null} if the pre/chain/post execution did not throw an {@code Exception}.
167     * @throws ServletException if any exception other than an {@code IOException} is thrown.
168     * @throws IOException      if the pre/chain/post execution throw an {@code IOException}
169     */
170    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
171            throws ServletException, IOException {
172        Exception exception = existing;
173        try {
174            afterCompletion(request, response, exception);
175            if (LOGGER.isTraceEnabled()) {
176                LOGGER.trace("Successfully invoked afterCompletion method.");
177            }
178        } catch (Exception e) {
179            if (exception == null) {
180                exception = e;
181            } else {
182                LOGGER.debug("afterCompletion implementation threw an exception.  This will be ignored to "
183                        + "allow the original source exception to be propagated.", e);
184            }
185        }
186        if (exception != null) {
187            if (exception instanceof ServletException) {
188                throw (ServletException) exception;
189            } else if (exception instanceof IOException) {
190                throw (IOException) exception;
191            } else {
192                if (LOGGER.isDebugEnabled()) {
193                    String msg = "Filter execution resulted in an unexpected Exception "
194                            + "(not IOException or ServletException as the Filter API recommends).  "
195                            + "Wrapping in ServletException and propagating.";
196                    LOGGER.debug(msg);
197                }
198                throw new ServletException(exception);
199            }
200        }
201    }
202}