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 &quot;{@code .FILTERED}&quot;.
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    }