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 log = 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) afterCompletion(request,response,exception)}
066     * implementation, which is guaranteed to be called for every request, even when the chain processing throws
067     * an Exception.
068     * <p/>
069     * The default implementation does nothing (no-op) and exists as a template method for subclasses.
070     *
071     * @param request  the incoming ServletRequest
072     * @param response the outgoing ServletResponse
073     * @throws Exception if an error occurs.
074     */
075    @SuppressWarnings({"UnusedDeclaration"})
076    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
077    }
078
079    /**
080     * Called in all cases in a {@code finally} block even if {@link #preHandle preHandle} returns
081     * {@code false} or if an exception is thrown during filter chain processing.  Can be used for resource
082     * cleanup if so desired.
083     * <p/>
084     * The default implementation does nothing (no-op) and exists as a template method for subclasses.
085     *
086     * @param request   the incoming ServletRequest
087     * @param response  the outgoing ServletResponse
088     * @param exception any exception thrown during {@link #preHandle preHandle}, {@link #executeChain executeChain},
089     *                  or {@link #postHandle postHandle} execution, or {@code null} if no exception was thrown
090     *                  (i.e. the chain processed successfully).
091     * @throws Exception if an error occurs.
092     */
093    @SuppressWarnings({"UnusedDeclaration"})
094    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
095    }
096
097    /**
098     * Actually executes the specified filter chain by calling <code>chain.doFilter(request,response);</code>.
099     * <p/>
100     * Can be overridden by subclasses for custom logic.
101     *
102     * @param request  the incoming ServletRequest
103     * @param response the outgoing ServletResponse
104     * @param chain    the filter chain to execute
105     * @throws Exception if there is any error executing the chain.
106     */
107    protected void executeChain(ServletRequest request, ServletResponse response, FilterChain chain) throws Exception {
108        chain.doFilter(request, response);
109    }
110
111    /**
112     * Actually implements the chain execution logic, utilizing
113     * {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) pre},
114     * {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) post}, and
115     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) after}
116     * advice hooks.
117     *
118     * @param request  the incoming ServletRequest
119     * @param response the outgoing ServletResponse
120     * @param chain    the filter chain to execute
121     * @throws ServletException if a servlet-related error occurs
122     * @throws IOException      if an IO error occurs
123     */
124    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
125            throws ServletException, IOException {
126
127        Exception exception = null;
128
129        try {
130
131            boolean continueChain = preHandle(request, response);
132            if (log.isTraceEnabled()) {
133                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");
134            }
135
136            if (continueChain) {
137                executeChain(request, response, chain);
138            }
139
140            postHandle(request, response);
141            if (log.isTraceEnabled()) {
142                log.trace("Successfully invoked postHandle method");
143            }
144
145        } catch (Exception e) {
146            exception = e;
147        } finally {
148            cleanup(request, response, exception);
149        }
150    }
151
152    /**
153     * Executes cleanup logic in the {@code finally} code block in the
154     * {@link #doFilterInternal(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilterInternal}
155     * implementation.
156     * <p/>
157     * This implementation specifically calls
158     * {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) afterCompletion}
159     * as well as handles any exceptions properly.
160     *
161     * @param request  the incoming {@code ServletRequest}
162     * @param response the outgoing {@code ServletResponse}
163     * @param existing any exception that might have occurred while executing the {@code FilterChain} or
164     *                 pre or post advice, or {@code null} if the pre/chain/post execution did not throw an {@code Exception}.
165     * @throws ServletException if any exception other than an {@code IOException} is thrown.
166     * @throws IOException      if the pre/chain/post execution throw an {@code IOException}
167     */
168    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing)
169            throws ServletException, IOException {
170        Exception exception = existing;
171        try {
172            afterCompletion(request, response, exception);
173            if (log.isTraceEnabled()) {
174                log.trace("Successfully invoked afterCompletion method.");
175            }
176        } catch (Exception e) {
177            if (exception == null) {
178                exception = e;
179            } else {
180                log.debug("afterCompletion implementation threw an exception.  This will be ignored to " +
181                        "allow the original source exception to be propagated.", e);
182            }
183        }
184        if (exception != null) {
185            if (exception instanceof ServletException) {
186                throw (ServletException) exception;
187            } else if (exception instanceof IOException) {
188                throw (IOException) exception;
189            } else {
190                if (log.isDebugEnabled()) {
191                    String msg = "Filter execution resulted in an unexpected Exception " +
192                            "(not IOException or ServletException as the Filter API recommends).  " +
193                            "Wrapping in ServletException and propagating.";
194                    log.debug(msg);
195                }
196                throw new ServletException(exception);
197            }
198        }
199    }
200}