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 }