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 /**
032 * Filter base class that guarantees to be just executed once per request,
033 * on any servlet container. It provides a {@link #doFilterInternal}
034 * method with HttpServletRequest and HttpServletResponse arguments.
035 * <p/>
036 * The {@link #getAlreadyFilteredAttributeName} method determines how
037 * to identify that a request is already filtered. The default implementation
038 * is based on the configured name of the concrete filter instance.
039 * <p/>
040 * <b>NOTE</b> This class was borrowed from the Spring framework, and as such,
041 * all copyright notices and author names have remained in tact.
042 *
043 * @since 0.1
044 */
045 public abstract class OncePerRequestFilter extends NameableFilter {
046
047 /**
048 * Private internal log instance.
049 */
050 private static final Logger log = LoggerFactory.getLogger(OncePerRequestFilter.class);
051
052 /**
053 * Suffix that gets appended to the filter name for the "already filtered" request attribute.
054 *
055 * @see #getAlreadyFilteredAttributeName
056 */
057 public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
058
059 /**
060 * This {@code doFilter} implementation stores a request attribute for
061 * "already filtered", proceeding without filtering again if the
062 * attribute is already there.
063 *
064 * @see #getAlreadyFilteredAttributeName
065 * @see #shouldNotFilter
066 * @see #doFilterInternal
067 */
068 public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
069 throws ServletException, IOException {
070 String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
071 if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(request)) {
072 log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());
073 // Proceed without invoking this filter...
074 filterChain.doFilter(request, response);
075 } else {
076 // Do invoke this filter...
077 log.trace("Filter '{}' not yet executed. Executing now.", getName());
078 request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
079
080 try {
081 doFilterInternal(request, response, filterChain);
082 } finally {
083 // Once the request has finished, we're done and we don't
084 // need to mark as 'already filtered' any more.
085 request.removeAttribute(alreadyFilteredAttributeName);
086 }
087 }
088 }
089
090 /**
091 * Return name of the request attribute that identifies that a request has already been filtered.
092 * <p/>
093 * The default implementation takes the configured {@link #getName() name} and appends "{@code .FILTERED}".
094 * If the filter is not fully initialized, it falls back to the implementation's class name.
095 *
096 * @return the name of the request attribute that identifies that a request has already been filtered.
097 * @see #getName
098 * @see #ALREADY_FILTERED_SUFFIX
099 */
100 protected String getAlreadyFilteredAttributeName() {
101 String name = getName();
102 if (name == null) {
103 name = getClass().getName();
104 }
105 return name + ALREADY_FILTERED_SUFFIX;
106 }
107
108 /**
109 * Can be overridden in subclasses for custom filtering control,
110 * returning <code>true</code> to avoid filtering of the given request.
111 * <p>The default implementation always returns <code>false</code>.
112 *
113 * @param request current HTTP request
114 * @return whether the given request should <i>not</i> be filtered
115 * @throws ServletException in case of errors
116 */
117 @SuppressWarnings({"UnusedDeclaration"})
118 protected boolean shouldNotFilter(ServletRequest request) throws ServletException {
119 return false;
120 }
121
122
123 /**
124 * Same contract as for
125 * {@link #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)},
126 * but guaranteed to be invoked only once per request.
127 *
128 * @param request incoming {@code ServletRequest}
129 * @param response outgoing {@code ServletResponse}
130 * @param chain the {@code FilterChain} to execute
131 * @throws ServletException if there is a problem processing the request
132 * @throws IOException if there is an I/O problem processing the request
133 */
134 protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
135 throws ServletException, IOException;
136 }