UrlPipeline.java
/*
* Copyright (C) 2009-2010, Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* https://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.http.server.glue;
import java.io.IOException;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Encapsulates the entire serving stack for a single URL.
* <p>
* Subclasses provide the implementation of {@link #match(HttpServletRequest)},
* which is called by {@link MetaServlet} in registration order to determine the
* pipeline that will be used to handle a request.
* <p>
* The very bottom of each pipeline is a single {@link HttpServlet} that will
* handle producing the response for this pipeline's URL. {@link Filter}s may
* also be registered and applied around the servlet's processing, to manage
* request attributes, set standard response headers, or completely override the
* response generation.
*/
abstract class UrlPipeline {
/** Filters to apply around {@link #servlet}; may be empty but never null. */
private final Filter[] filters;
/** Instance that must generate the response; never null. */
private final HttpServlet servlet;
UrlPipeline(Filter[] filters, HttpServlet servlet) {
this.filters = filters;
this.servlet = servlet;
}
/**
* Initialize all contained filters and servlets.
*
* @param context
* the servlet container context our {@link MetaServlet} is
* running within.
* @param inited
* <i>(input/output)</i> the set of filters and servlets which
* have already been initialized within the container context. If
* those same instances appear in this pipeline they are not
* initialized a second time. Filters and servlets that are first
* initialized by this pipeline will be added to this set.
* @throws ServletException
* a filter or servlet is unable to initialize.
*/
void init(ServletContext context, Set<Object> inited)
throws ServletException {
for (Filter ref : filters)
initFilter(ref, context, inited);
initServlet(servlet, context, inited);
}
private static void initFilter(final Filter ref,
final ServletContext context, final Set<Object> inited)
throws ServletException {
if (!inited.contains(ref)) {
ref.init(new NoParameterFilterConfig(ref.getClass().getName(),
context));
inited.add(ref);
}
}
private static void initServlet(final HttpServlet ref,
final ServletContext context, final Set<Object> inited)
throws ServletException {
if (!inited.contains(ref)) {
ref.init(new ServletConfig() {
@Override
public String getInitParameter(String name) {
return null;
}
@Override
public Enumeration<String> getInitParameterNames() {
return new Enumeration<>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
throw new NoSuchElementException();
}
};
}
@Override
public ServletContext getServletContext() {
return context;
}
@Override
public String getServletName() {
return ref.getClass().getName();
}
});
inited.add(ref);
}
}
/**
* Destroy all contained filters and servlets.
*
* @param destroyed
* <i>(input/output)</i> the set of filters and servlets which
* have already been destroyed within the container context. If
* those same instances appear in this pipeline they are not
* destroyed a second time. Filters and servlets that are first
* destroyed by this pipeline will be added to this set.
*/
void destroy(Set<Object> destroyed) {
for (Filter ref : filters)
destroyFilter(ref, destroyed);
destroyServlet(servlet, destroyed);
}
private static void destroyFilter(Filter ref, Set<Object> destroyed) {
if (!destroyed.contains(ref)) {
ref.destroy();
destroyed.add(ref);
}
}
private static void destroyServlet(HttpServlet ref, Set<Object> destroyed) {
if (!destroyed.contains(ref)) {
ref.destroy();
destroyed.add(ref);
}
}
/**
* Determine if this pipeline handles the request's URL.
* <p>
* This method should match on the request's {@code getPathInfo()} method,
* as {@link MetaServlet} passes the request along as-is to each pipeline's
* match method.
*
* @param req
* current HTTP request being considered by {@link MetaServlet}.
* @return {@code true} if this pipeline is configured to handle the
* request; {@code false} otherwise.
*/
abstract boolean match(HttpServletRequest req);
/**
* Execute the filters and the servlet on the request.
* <p>
* Invoked by {@link MetaServlet} once {@link #match(HttpServletRequest)}
* has determined this pipeline is the correct pipeline to handle the
* current request.
*
* @param req
* current HTTP request.
* @param rsp
* current HTTP response.
* @throws ServletException
* request cannot be completed.
* @throws IOException
* IO error prevents the request from being completed.
*/
void service(HttpServletRequest req, HttpServletResponse rsp)
throws ServletException, IOException {
if (0 < filters.length)
new Chain(filters, servlet).doFilter(req, rsp);
else
servlet.service(req, rsp);
}
private static class Chain implements FilterChain {
private final Filter[] filters;
private final HttpServlet servlet;
private int filterIdx;
Chain(Filter[] filters, HttpServlet servlet) {
this.filters = filters;
this.servlet = servlet;
}
@Override
public void doFilter(ServletRequest req, ServletResponse rsp)
throws IOException, ServletException {
if (filterIdx < filters.length)
filters[filterIdx++].doFilter(req, rsp, this);
else
servlet.service(req, rsp);
}
}
}