blob: 6f6af416d12573d83d7c23106ed3b7ceef46b94d [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AbstractShiroFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Apache Shiro :: Web</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.web.servlet</a> &gt; <span class="el_source">AbstractShiroFilter.java</span></div><h1>AbstractShiroFilter.java</h1><pre class="source lang-java linenums">/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* &quot;License&quot;); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.web.servlet;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.ExecutionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.apache.shiro.web.subject.WebSubject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.Callable;
/**
* Abstract base class that provides all standard Shiro request filtering behavior and expects
* subclasses to implement configuration-specific logic (INI, XML, .properties, etc).
* &lt;p/&gt;
* Subclasses should perform configuration and construction logic in an overridden
* {@link #init()} method implementation. That implementation should make available any constructed
* {@code SecurityManager} and {@code FilterChainResolver} by calling
* {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and
* {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively.
* &lt;h3&gt;Static SecurityManager&lt;/h3&gt;
* By default the {@code SecurityManager} instance enabled by this filter &lt;em&gt;will not&lt;/em&gt; be enabled in static
* memory via the {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}
* method. Instead, it is expected that Subject instances will always be constructed on a request-processing thread
* via instances of this Filter class.
* &lt;p/&gt;
* However, if you need to construct {@code Subject} instances on separate (non request-processing) threads, it might
* be easiest to enable the SecurityManager to be available in static memory via the
* {@link SecurityUtils#getSecurityManager()} method. You can do this by additionally specifying an {@code init-param}:
* &lt;pre&gt;
* &amp;lt;filter&amp;gt;
* ... other config here ...
* &amp;lt;init-param&amp;gt;
* &amp;lt;param-name&amp;gt;staticSecurityManagerEnabled&amp;lt;/param-name&amp;gt;
* &amp;lt;param-value&amp;gt;true&amp;lt;/param-value&amp;gt;
* &amp;lt;/init-param&amp;gt;
* &amp;lt;/filter&amp;gt;
* &lt;/pre&gt;
* See the Shiro &lt;a href=&quot;http://shiro.apache.org/subject.html&quot;&gt;Subject documentation&lt;/a&gt; for more information as to
* if you would do this, particularly the sections on the {@code Subject.Builder} and Thread Association.
*
* @since 1.0
* @see &lt;a href=&quot;http://shiro.apache.org/subject.html&quot;&gt;Subject documentation&lt;/a&gt;
*/
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
<span class="fc" id="L76"> private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class);</span>
private static final String STATIC_INIT_PARAM_NAME = &quot;staticSecurityManagerEnabled&quot;;
// Reference to the security manager used by this filter
private WebSecurityManager securityManager;
// Used to determine which chain should handle an incoming request/response
private FilterChainResolver filterChainResolver;
/**
* Whether or not to bind the constructed SecurityManager instance to static memory (via
* SecurityUtils.setSecurityManager). This was added to support https://issues.apache.org/jira/browse/SHIRO-287
* @since 1.2
*/
private boolean staticSecurityManagerEnabled;
<span class="fc" id="L93"> protected AbstractShiroFilter() {</span>
<span class="fc" id="L94"> this.staticSecurityManagerEnabled = false;</span>
<span class="fc" id="L95"> }</span>
public WebSecurityManager getSecurityManager() {
<span class="fc" id="L98"> return securityManager;</span>
}
public void setSecurityManager(WebSecurityManager sm) {
<span class="fc" id="L102"> this.securityManager = sm;</span>
<span class="fc" id="L103"> }</span>
public FilterChainResolver getFilterChainResolver() {
<span class="fc" id="L106"> return filterChainResolver;</span>
}
public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
<span class="fc" id="L110"> this.filterChainResolver = filterChainResolver;</span>
<span class="fc" id="L111"> }</span>
/**
* Returns {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
* to static memory (via
* {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
* {@code false} otherwise.
* &lt;p/&gt;
* The default value is {@code false}.
* &lt;p/&gt;
*
*
* @return {@code true} if the constructed {@link #getSecurityManager() securityManager} reference should be bound
* to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}),
* {@code false} otherwise.
* @since 1.2
* @see &lt;a href=&quot;https://issues.apache.org/jira/browse/SHIRO-287&quot;&gt;SHIRO-287&lt;/a&gt;
*/
public boolean isStaticSecurityManagerEnabled() {
<span class="fc" id="L130"> return staticSecurityManagerEnabled;</span>
}
/**
* Sets if the constructed {@link #getSecurityManager() securityManager} reference should be bound
* to static memory (via {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
* &lt;p/&gt;
* The default value is {@code false}.
*
* @param staticSecurityManagerEnabled if the constructed {@link #getSecurityManager() securityManager} reference
* should be bound to static memory (via
* {@code SecurityUtils.}{@link SecurityUtils#setSecurityManager(org.apache.shiro.mgt.SecurityManager) setSecurityManager}).
* @since 1.2
* @see &lt;a href=&quot;https://issues.apache.org/jira/browse/SHIRO-287&quot;&gt;SHIRO-287&lt;/a&gt;
*/
public void setStaticSecurityManagerEnabled(boolean staticSecurityManagerEnabled) {
<span class="fc" id="L146"> this.staticSecurityManagerEnabled = staticSecurityManagerEnabled;</span>
<span class="fc" id="L147"> }</span>
protected final void onFilterConfigSet() throws Exception {
//added in 1.2 for SHIRO-287:
<span class="fc" id="L151"> applyStaticSecurityManagerEnabledConfig();</span>
<span class="fc" id="L152"> init();</span>
<span class="fc" id="L153"> ensureSecurityManager();</span>
//added in 1.2 for SHIRO-287:
<span class="fc bfc" id="L155" title="All 2 branches covered."> if (isStaticSecurityManagerEnabled()) {</span>
<span class="fc" id="L156"> SecurityUtils.setSecurityManager(getSecurityManager());</span>
}
<span class="fc" id="L158"> }</span>
/**
* Checks if the init-param that configures the filter to use static memory has been configured, and if so,
* sets the {@link #setStaticSecurityManagerEnabled(boolean)} attribute with the configured value.
*
* @since 1.2
* @see &lt;a href=&quot;https://issues.apache.org/jira/browse/SHIRO-287&quot;&gt;SHIRO-287&lt;/a&gt;
*/
private void applyStaticSecurityManagerEnabledConfig() {
<span class="fc" id="L168"> String value = getInitParam(STATIC_INIT_PARAM_NAME);</span>
<span class="fc bfc" id="L169" title="All 2 branches covered."> if (value != null) {</span>
<span class="fc" id="L170"> Boolean b = Boolean.valueOf(value);</span>
<span class="pc bpc" id="L171" title="1 of 2 branches missed."> if (b != null) {</span>
<span class="fc" id="L172"> setStaticSecurityManagerEnabled(b);</span>
}
}
<span class="fc" id="L175"> }</span>
public void init() throws Exception {
<span class="fc" id="L178"> }</span>
/**
* A fallback mechanism called in {@link #onFilterConfigSet()} to ensure that the
* {@link #getSecurityManager() securityManager} property has been set by configuration, and if not,
* creates one automatically.
*/
private void ensureSecurityManager() {
<span class="fc" id="L186"> WebSecurityManager securityManager = getSecurityManager();</span>
<span class="pc bpc" id="L187" title="1 of 2 branches missed."> if (securityManager == null) {</span>
<span class="nc" id="L188"> log.info(&quot;No SecurityManager configured. Creating default.&quot;);</span>
<span class="nc" id="L189"> securityManager = createDefaultSecurityManager();</span>
<span class="nc" id="L190"> setSecurityManager(securityManager);</span>
}
<span class="fc" id="L192"> }</span>
protected WebSecurityManager createDefaultSecurityManager() {
<span class="nc" id="L195"> return new DefaultWebSecurityManager();</span>
}
protected boolean isHttpSessions() {
<span class="nc" id="L199"> return getSecurityManager().isHttpSessionMode();</span>
}
/**
* Wraps the original HttpServletRequest in a {@link ShiroHttpServletRequest}, which is required for supporting
* Servlet Specification behavior backed by a {@link org.apache.shiro.subject.Subject Subject} instance.
*
* @param orig the original Servlet Container-provided incoming {@code HttpServletRequest} instance.
* @return {@link ShiroHttpServletRequest ShiroHttpServletRequest} instance wrapping the original.
* @since 1.0
*/
protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
<span class="nc" id="L211"> return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());</span>
}
/**
* Prepares the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request
* processing.
* &lt;p/&gt;
* If the {@code ServletRequest} is an instance of {@link HttpServletRequest}, the value returned from this method
* is obtained by calling {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)} to allow Shiro-specific
* HTTP behavior, otherwise the original {@code ServletRequest} argument is returned.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param chain the Servlet Container provided {@code FilterChain} that will receive the returned request.
* @return the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request processing.
* @since 1.0
*/
@SuppressWarnings({&quot;UnusedDeclaration&quot;})
protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
<span class="nc" id="L230"> ServletRequest toUse = request;</span>
<span class="nc bnc" id="L231" title="All 2 branches missed."> if (request instanceof HttpServletRequest) {</span>
<span class="nc" id="L232"> HttpServletRequest http = (HttpServletRequest) request;</span>
<span class="nc" id="L233"> toUse = wrapServletRequest(http);</span>
}
<span class="nc" id="L235"> return toUse;</span>
}
/**
* Returns a new {@link ShiroHttpServletResponse} instance, wrapping the {@code orig} argument, in order to provide
* correct URL rewriting behavior required by the Servlet Specification when using Shiro-based sessions (and not
* Servlet Container HTTP-based sessions).
*
* @param orig the original {@code HttpServletResponse} instance provided by the Servlet Container.
* @param request the {@code ShiroHttpServletRequest} instance wrapping the original request.
* @return the wrapped ServletResponse instance to use during {@link FilterChain} execution.
* @since 1.0
*/
protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
<span class="nc" id="L249"> return new ShiroHttpServletResponse(orig, getServletContext(), request);</span>
}
/**
* Prepares the {@code ServletResponse} instance that will be passed to the {@code FilterChain} for request
* processing.
* &lt;p/&gt;
* This implementation delegates to {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)}
* only if Shiro-based sessions are enabled (that is, !{@link #isHttpSessions()}) and the request instance is a
* {@link ShiroHttpServletRequest}. This ensures that any URL rewriting that occurs is handled correctly using the
* Shiro-managed Session's sessionId and not a servlet container session ID.
* &lt;p/&gt;
* If HTTP-based sessions are enabled (the default), then this method does nothing and just returns the
* {@code ServletResponse} argument as-is, relying on the default Servlet Container URL rewriting logic.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param chain the Servlet Container provided {@code FilterChain} that will receive the returned request.
* @return the {@code ServletResponse} instance that will be passed to the {@code FilterChain} during request processing.
* @since 1.0
*/
@SuppressWarnings({&quot;UnusedDeclaration&quot;})
protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
<span class="nc" id="L272"> ServletResponse toUse = response;</span>
<span class="nc bnc" id="L273" title="All 6 branches missed."> if (!isHttpSessions() &amp;&amp; (request instanceof ShiroHttpServletRequest) &amp;&amp;</span>
(response instanceof HttpServletResponse)) {
//the ShiroHttpServletResponse exists to support URL rewriting for session ids. This is only needed if
//using Shiro sessions (i.e. not simple HttpSession based sessions):
<span class="nc" id="L277"> toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);</span>
}
<span class="nc" id="L279"> return toUse;</span>
}
/**
* Creates a {@link WebSubject} instance to associate with the incoming request/response pair which will be used
* throughout the request/response execution.
*
* @param request the incoming {@code ServletRequest}
* @param response the outgoing {@code ServletResponse}
* @return the {@code WebSubject} instance to associate with the request/response execution
* @since 1.0
*/
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
<span class="nc" id="L292"> return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();</span>
}
/**
* Updates any 'native' Session's last access time that might exist to the timestamp when this method is called.
* If native sessions are not enabled (that is, standard Servlet container sessions are being used) or there is no
* session ({@code subject.getSession(false) == null}), this method does nothing.
* &lt;p/&gt;This method implementation merely calls
* &lt;code&gt;Session.{@link org.apache.shiro.session.Session#touch() touch}()&lt;/code&gt; on the session.
*
* @param request incoming request - ignored, but available to subclasses that might wish to override this method
* @param response outgoing response - ignored, but available to subclasses that might wish to override this method
* @since 1.0
*/
@SuppressWarnings({&quot;UnusedDeclaration&quot;})
protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
<span class="nc bnc" id="L308" title="All 2 branches missed."> if (!isHttpSessions()) { //'native' sessions</span>
<span class="nc" id="L309"> Subject subject = SecurityUtils.getSubject();</span>
//Subject should never _ever_ be null, but just in case:
<span class="nc bnc" id="L311" title="All 2 branches missed."> if (subject != null) {</span>
<span class="nc" id="L312"> Session session = subject.getSession(false);</span>
<span class="nc bnc" id="L313" title="All 2 branches missed."> if (session != null) {</span>
try {
<span class="nc" id="L315"> session.touch();</span>
<span class="nc" id="L316"> } catch (Throwable t) {</span>
<span class="nc" id="L317"> log.error(&quot;session.touch() method invocation has failed. Unable to update&quot; +</span>
&quot;the corresponding session's last access time based on the incoming request.&quot;, t);
<span class="nc" id="L319"> }</span>
}
}
}
<span class="nc" id="L323"> }</span>
/**
* {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request. It
* performs the following ordered operations:
* &lt;ol&gt;
* &lt;li&gt;{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
* the incoming {@code ServletRequest} for use during Shiro's processing&lt;/li&gt;
* &lt;li&gt;{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
* the outgoing {@code ServletResponse} for use during Shiro's processing&lt;/li&gt;
* &lt;li&gt; {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
* {@link Subject} instance based on the specified request/response pair.&lt;/li&gt;
* &lt;li&gt;Finally {@link Subject#execute(Runnable) executes} the
* {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
* {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
* methods&lt;/li&gt;
* &lt;/ol&gt;
* &lt;p/&gt;
* The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
* implementation technique to guarantee proper thread binding and restoration is completed successfully.
*
* @param servletRequest the incoming {@code ServletRequest}
* @param servletResponse the outgoing {@code ServletResponse}
* @param chain the container-provided {@code FilterChain} to execute
* @throws IOException if an IO error occurs
* @throws javax.servlet.ServletException if an Throwable other than an IOException
*/
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
<span class="nc" id="L353"> Throwable t = null;</span>
try {
<span class="nc" id="L356"> final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);</span>
<span class="nc" id="L357"> final ServletResponse response = prepareServletResponse(request, servletResponse, chain);</span>
<span class="nc" id="L359"> final Subject subject = createSubject(request, response);</span>
//noinspection unchecked
<span class="nc" id="L362"> subject.execute(new Callable() {</span>
public Object call() throws Exception {
<span class="nc" id="L364"> updateSessionLastAccessTime(request, response);</span>
<span class="nc" id="L365"> executeChain(request, response, chain);</span>
<span class="nc" id="L366"> return null;</span>
}
});
<span class="nc" id="L369"> } catch (ExecutionException ex) {</span>
<span class="nc" id="L370"> t = ex.getCause();</span>
<span class="nc" id="L371"> } catch (Throwable throwable) {</span>
<span class="nc" id="L372"> t = throwable;</span>
<span class="nc" id="L373"> }</span>
<span class="nc bnc" id="L375" title="All 2 branches missed."> if (t != null) {</span>
<span class="nc bnc" id="L376" title="All 2 branches missed."> if (t instanceof ServletException) {</span>
<span class="nc" id="L377"> throw (ServletException) t;</span>
}
<span class="nc bnc" id="L379" title="All 2 branches missed."> if (t instanceof IOException) {</span>
<span class="nc" id="L380"> throw (IOException) t;</span>
}
//otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
<span class="nc" id="L383"> String msg = &quot;Filtered request failed.&quot;;</span>
<span class="nc" id="L384"> throw new ServletException(msg, t);</span>
}
<span class="nc" id="L386"> }</span>
/**
* Returns the {@code FilterChain} to execute for the given request.
* &lt;p/&gt;
* The {@code origChain} argument is the
* original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
* more behavior by pre-pending further chains according to the Shiro configuration.
* &lt;p/&gt;
* This implementation returns the chain that will actually be executed by acquiring the chain from a
* {@link #getFilterChainResolver() filterChainResolver}. The resolver determines exactly which chain to
* execute, typically based on URL configuration. If no chain is returned from the resolver call
* (returns {@code null}), then the {@code origChain} will be returned by default.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param origChain the original {@code FilterChain} provided by the Servlet Container
* @return the {@link FilterChain} to execute for the given request
* @since 1.0
*/
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
<span class="nc" id="L407"> FilterChain chain = origChain;</span>
<span class="nc" id="L409"> FilterChainResolver resolver = getFilterChainResolver();</span>
<span class="nc bnc" id="L410" title="All 2 branches missed."> if (resolver == null) {</span>
<span class="nc" id="L411"> log.debug(&quot;No FilterChainResolver configured. Returning original FilterChain.&quot;);</span>
<span class="nc" id="L412"> return origChain;</span>
}
<span class="nc" id="L415"> FilterChain resolved = resolver.getChain(request, response, origChain);</span>
<span class="nc bnc" id="L416" title="All 2 branches missed."> if (resolved != null) {</span>
<span class="nc" id="L417"> log.trace(&quot;Resolved a configured FilterChain for the current request.&quot;);</span>
<span class="nc" id="L418"> chain = resolved;</span>
} else {
<span class="nc" id="L420"> log.trace(&quot;No FilterChain configured for the current request. Using the default.&quot;);</span>
}
<span class="nc" id="L423"> return chain;</span>
}
/**
* Executes a {@link FilterChain} for the given request.
* &lt;p/&gt;
* This implementation first delegates to
* &lt;code&gt;{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}&lt;/code&gt;
* to allow the application's Shiro configuration to determine exactly how the chain should execute. The resulting
* value from that call is then executed directly by calling the returned {@code FilterChain}'s
* {@link FilterChain#doFilter doFilter} method. That is:
* &lt;pre&gt;
* FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
* chain.{@link FilterChain#doFilter doFilter}(request,response);&lt;/pre&gt;
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
* chain of Filters.
* @throws IOException if the underlying {@code chain.doFilter} call results in an IOException
* @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
* @since 1.0
*/
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
<span class="nc" id="L448"> FilterChain chain = getExecutionChain(request, response, origChain);</span>
<span class="nc" id="L449"> chain.doFilter(request, response);</span>
<span class="nc" id="L450"> }</span>
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.3.201901230119</span></div></body></html>