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     */
019    package org.apache.shiro.web.servlet;
020    
021    import org.slf4j.Logger;
022    import org.slf4j.LoggerFactory;
023    
024    import javax.servlet.FilterChain;
025    import javax.servlet.ServletException;
026    import javax.servlet.ServletRequest;
027    import javax.servlet.ServletResponse;
028    import 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     */
039    public 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    }