Browse Source

Merge "Refactor HTTP server stack to use Filter as base"

stable-1.2
Shawn O. Pearce 13 years ago committed by Code Review
parent
commit
f3e37b5530
  1. 308
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
  2. 190
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java
  3. 222
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java
  4. 142
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
  5. 85
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java
  6. 2
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java
  7. 4
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java
  8. 28
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java

308
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java

@ -0,0 +1,308 @@
/*
* Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.http.server;
import java.io.File;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.http.server.glue.ErrorServlet;
import org.eclipse.jgit.http.server.glue.MetaFilter;
import org.eclipse.jgit.http.server.glue.RegexGroupFilter;
import org.eclipse.jgit.http.server.glue.ServletBinder;
import org.eclipse.jgit.http.server.resolver.AsIsFileService;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.FileResolver;
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import org.eclipse.jgit.util.StringUtils;
/**
* Handles Git repository access over HTTP.
* <p>
* Applications embedding this filter should map a directory path within the
* application to this filter. For a servlet version, see {@link GitServlet}.
* <p>
* Applications may wish to add additional repository action URLs to this
* servlet by taking advantage of its extension from {@link MetaFilter}.
* Callers may register their own URL suffix translations through
* {@link #serve(String)}, or their regex translations through
* {@link #serveRegex(String)}. Each translation should contain a complete
* filter pipeline which ends with the HttpServlet that should handle the
* requested action.
*/
public class GitFilter extends MetaFilter {
private volatile boolean initialized;
private RepositoryResolver<HttpServletRequest> resolver;
private AsIsFileService asIs = new AsIsFileService();
private UploadPackFactory<HttpServletRequest> uploadPackFactory = new DefaultUploadPackFactory();
private ReceivePackFactory<HttpServletRequest> receivePackFactory = new DefaultReceivePackFactory();
private final List<Filter> uploadPackFilters = new LinkedList<Filter>();
private final List<Filter> receivePackFilters = new LinkedList<Filter>();
/**
* New servlet that will load its base directory from {@code web.xml}.
* <p>
* The required parameter {@code base-path} must be configured to point to
* the local filesystem directory where all served Git repositories reside.
*/
public GitFilter() {
// Initialized above by field declarations.
}
/**
* New servlet configured with a specific resolver.
*
* @param resolver
* the resolver to use when matching URL to Git repository. If
* null the {@code base-path} parameter will be looked for in the
* parameter table during init, which usually comes from the
* {@code web.xml} file of the web application.
*/
public void setRepositoryResolver(RepositoryResolver<HttpServletRequest> resolver) {
assertNotInitialized();
this.resolver = resolver;
}
/**
* @param f
* the filter to validate direct access to repository files
* through a dumb client. If {@code null} then dumb client
* support is completely disabled.
*/
public void setAsIsFileService(AsIsFileService f) {
assertNotInitialized();
this.asIs = f != null ? f : AsIsFileService.DISABLED;
}
/**
* @param f
* the factory to construct and configure an {@link UploadPack}
* session when a fetch or clone is requested by a client.
*/
@SuppressWarnings("unchecked")
public void setUploadPackFactory(UploadPackFactory<HttpServletRequest> f) {
assertNotInitialized();
this.uploadPackFactory = f != null ? f : (UploadPackFactory<HttpServletRequest>)UploadPackFactory.DISABLED;
}
/**
* @param filter
* filter to apply before any of the UploadPack operations. The
* UploadPack instance is available in the request attribute
* {@link ServletUtils#ATTRIBUTE_HANDLER}.
*/
public void addUploadPackFilter(Filter filter) {
assertNotInitialized();
uploadPackFilters.add(filter);
}
/**
* @param f
* the factory to construct and configure a {@link ReceivePack}
* session when a push is requested by a client.
*/
@SuppressWarnings("unchecked")
public void setReceivePackFactory(ReceivePackFactory<HttpServletRequest> f) {
assertNotInitialized();
this.receivePackFactory = f != null ? f : (ReceivePackFactory<HttpServletRequest>)ReceivePackFactory.DISABLED;
}
/**
* @param filter
* filter to apply before any of the ReceivePack operations. The
* ReceivePack instance is available in the request attribute
* {@link ServletUtils#ATTRIBUTE_HANDLER}.
*/
public void addReceivePackFilter(Filter filter) {
assertNotInitialized();
receivePackFilters.add(filter);
}
private void assertNotInitialized() {
if (initialized)
throw new IllegalStateException(HttpServerText.get().alreadyInitializedByContainer);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
if (resolver == null) {
File root = getFile(filterConfig, "base-path");
boolean exportAll = getBoolean(filterConfig, "export-all");
setRepositoryResolver(new FileResolver<HttpServletRequest>(root, exportAll));
}
initialized = true;
if (uploadPackFactory != UploadPackFactory.DISABLED) {
ServletBinder b = serve("*/git-upload-pack");
b = b.through(new UploadPackServlet.Factory(uploadPackFactory));
for (Filter f : uploadPackFilters)
b = b.through(f);
b.with(new UploadPackServlet());
}
if (receivePackFactory != ReceivePackFactory.DISABLED) {
ServletBinder b = serve("*/git-receive-pack");
b = b.through(new ReceivePackServlet.Factory(receivePackFactory));
for (Filter f : receivePackFilters)
b = b.through(f);
b.with(new ReceivePackServlet());
}
ServletBinder refs = serve("*/" + Constants.INFO_REFS);
if (uploadPackFactory != UploadPackFactory.DISABLED) {
refs = refs.through(new UploadPackServlet.InfoRefs(
uploadPackFactory, uploadPackFilters));
}
if (receivePackFactory != ReceivePackFactory.DISABLED) {
refs = refs.through(new ReceivePackServlet.InfoRefs(
receivePackFactory, receivePackFilters));
}
if (asIs != AsIsFileService.DISABLED) {
refs = refs.through(new IsLocalFilter());
refs = refs.through(new AsIsFileFilter(asIs));
refs.with(new InfoRefsServlet());
} else
refs.with(new ErrorServlet(HttpServletResponse.SC_FORBIDDEN));
if (asIs != AsIsFileService.DISABLED) {
final IsLocalFilter mustBeLocal = new IsLocalFilter();
final AsIsFileFilter enabled = new AsIsFileFilter(asIs);
serve("*/" + Constants.HEAD)//
.through(mustBeLocal)//
.through(enabled)//
.with(new TextFileServlet(Constants.HEAD));
final String info_alternates = "objects/info/alternates";
serve("*/" + info_alternates)//
.through(mustBeLocal)//
.through(enabled)//
.with(new TextFileServlet(info_alternates));
final String http_alternates = "objects/info/http-alternates";
serve("*/" + http_alternates)//
.through(mustBeLocal)//
.through(enabled)//
.with(new TextFileServlet(http_alternates));
serve("*/objects/info/packs")//
.through(mustBeLocal)//
.through(enabled)//
.with(new InfoPacksServlet());
serveRegex("^/(.*)/objects/([0-9a-f]{2}/[0-9a-f]{38})$")//
.through(mustBeLocal)//
.through(enabled)//
.through(new RegexGroupFilter(2))//
.with(new ObjectFileServlet.Loose());
serveRegex("^/(.*)/objects/(pack/pack-[0-9a-f]{40}\\.pack)$")//
.through(mustBeLocal)//
.through(enabled)//
.through(new RegexGroupFilter(2))//
.with(new ObjectFileServlet.Pack());
serveRegex("^/(.*)/objects/(pack/pack-[0-9a-f]{40}\\.idx)$")//
.through(mustBeLocal)//
.through(enabled)//
.through(new RegexGroupFilter(2))//
.with(new ObjectFileServlet.PackIdx());
}
}
private static File getFile(FilterConfig cfg, String param)
throws ServletException {
String n = cfg.getInitParameter(param);
if (n == null || "".equals(n))
throw new ServletException(MessageFormat.format(HttpServerText.get().parameterNotSet, param));
File path = new File(n);
if (!path.exists())
throw new ServletException(MessageFormat.format(HttpServerText.get().pathForParamNotFound, path, param));
return path;
}
private static boolean getBoolean(FilterConfig cfg, String param)
throws ServletException {
String n = cfg.getInitParameter(param);
if (n == null)
return false;
try {
return StringUtils.toBoolean(n);
} catch (IllegalArgumentException err) {
throw new ServletException(MessageFormat.format(HttpServerText.get().invalidBoolean, param, n));
}
}
@Override
protected ServletBinder register(ServletBinder binder) {
if (resolver == null)
throw new IllegalStateException(HttpServerText.get().noResolverAvailable);
binder = binder.through(new NoCacheFilter());
binder = binder.through(new RepositoryFilter(resolver));
return binder;
}
}

190
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java

@ -43,32 +43,22 @@
package org.eclipse.jgit.http.server;
import java.io.File;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.http.server.glue.ErrorServlet;
import org.eclipse.jgit.http.server.glue.MetaServlet;
import org.eclipse.jgit.http.server.glue.RegexGroupFilter;
import org.eclipse.jgit.http.server.glue.ServletBinder;
import org.eclipse.jgit.http.server.resolver.AsIsFileService;
import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.FileResolver;
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import org.eclipse.jgit.util.StringUtils;
/**
* Handles Git repository access over HTTP.
@ -107,19 +97,7 @@ import org.eclipse.jgit.util.StringUtils;
public class GitServlet extends MetaServlet {
private static final long serialVersionUID = 1L;
private volatile boolean initialized;
private RepositoryResolver<HttpServletRequest> resolver;
private AsIsFileService asIs = new AsIsFileService();
private UploadPackFactory<HttpServletRequest> uploadPackFactory = new DefaultUploadPackFactory();
private ReceivePackFactory<HttpServletRequest> receivePackFactory = new DefaultReceivePackFactory();
private final List<Filter> uploadPackFilters = new LinkedList<Filter>();
private final List<Filter> receivePackFilters = new LinkedList<Filter>();
private final GitFilter gitFilter;
/**
* New servlet that will load its base directory from {@code web.xml}.
@ -128,7 +106,8 @@ public class GitServlet extends MetaServlet {
* the local filesystem directory where all served Git repositories reside.
*/
public GitServlet() {
// Initialized above by field declarations.
super(new GitFilter());
gitFilter = (GitFilter) getDelegateFilter();
}
/**
@ -141,8 +120,7 @@ public class GitServlet extends MetaServlet {
* {@code web.xml} file of the web application.
*/
public void setRepositoryResolver(RepositoryResolver<HttpServletRequest> resolver) {
assertNotInitialized();
this.resolver = resolver;
gitFilter.setRepositoryResolver(resolver);
}
/**
@ -152,8 +130,7 @@ public class GitServlet extends MetaServlet {
* support is completely disabled.
*/
public void setAsIsFileService(AsIsFileService f) {
assertNotInitialized();
this.asIs = f != null ? f : AsIsFileService.DISABLED;
gitFilter.setAsIsFileService(f);
}
/**
@ -163,8 +140,7 @@ public class GitServlet extends MetaServlet {
*/
@SuppressWarnings("unchecked")
public void setUploadPackFactory(UploadPackFactory<HttpServletRequest> f) {
assertNotInitialized();
this.uploadPackFactory = f != null ? f : (UploadPackFactory<HttpServletRequest>)UploadPackFactory.DISABLED;
gitFilter.setUploadPackFactory(f);
}
/**
@ -174,8 +150,7 @@ public class GitServlet extends MetaServlet {
* {@link ServletUtils#ATTRIBUTE_HANDLER}.
*/
public void addUploadPackFilter(Filter filter) {
assertNotInitialized();
uploadPackFilters.add(filter);
gitFilter.addUploadPackFilter(filter);
}
/**
@ -185,8 +160,7 @@ public class GitServlet extends MetaServlet {
*/
@SuppressWarnings("unchecked")
public void setReceivePackFactory(ReceivePackFactory<HttpServletRequest> f) {
assertNotInitialized();
this.receivePackFactory = f != null ? f : (ReceivePackFactory<HttpServletRequest>)ReceivePackFactory.DISABLED;
gitFilter.setReceivePackFactory(f);
}
/**
@ -196,133 +170,27 @@ public class GitServlet extends MetaServlet {
* {@link ServletUtils#ATTRIBUTE_HANDLER}.
*/
public void addReceivePackFilter(Filter filter) {
assertNotInitialized();
receivePackFilters.add(filter);
}
private void assertNotInitialized() {
if (initialized)
throw new IllegalStateException(HttpServerText.get().alreadyInitializedByContainer);
gitFilter.addReceivePackFilter(filter);
}
@Override
public void init(final ServletConfig config) throws ServletException {
super.init(config);
if (resolver == null) {
final File root = getFile("base-path");
final boolean exportAll = getBoolean("export-all");
setRepositoryResolver(new FileResolver<HttpServletRequest>(root, exportAll));
}
initialized = true;
if (uploadPackFactory != UploadPackFactory.DISABLED) {
ServletBinder b = serve("*/git-upload-pack");
b = b.through(new UploadPackServlet.Factory(uploadPackFactory));
for (Filter f : uploadPackFilters)
b = b.through(f);
b.with(new UploadPackServlet());
}
if (receivePackFactory != ReceivePackFactory.DISABLED) {
ServletBinder b = serve("*/git-receive-pack");
b = b.through(new ReceivePackServlet.Factory(receivePackFactory));
for (Filter f : receivePackFilters)
b = b.through(f);
b.with(new ReceivePackServlet());
}
ServletBinder refs = serve("*/" + Constants.INFO_REFS);
if (uploadPackFactory != UploadPackFactory.DISABLED) {
refs = refs.through(new UploadPackServlet.InfoRefs(
uploadPackFactory, uploadPackFilters));
}
if (receivePackFactory != ReceivePackFactory.DISABLED) {
refs = refs.through(new ReceivePackServlet.InfoRefs(
receivePackFactory, receivePackFilters));
}
if (asIs != AsIsFileService.DISABLED) {
refs = refs.through(new IsLocalFilter());
refs = refs.through(new AsIsFileFilter(asIs));
refs.with(new InfoRefsServlet());
} else
refs.with(new ErrorServlet(HttpServletResponse.SC_FORBIDDEN));
if (asIs != AsIsFileService.DISABLED) {
final IsLocalFilter mustBeLocal = new IsLocalFilter();
final AsIsFileFilter enabled = new AsIsFileFilter(asIs);
serve("*/" + Constants.HEAD)//
.through(mustBeLocal)//
.through(enabled)//
.with(new TextFileServlet(Constants.HEAD));
final String info_alternates = "objects/info/alternates";
serve("*/" + info_alternates)//
.through(mustBeLocal)//
.through(enabled)//
.with(new TextFileServlet(info_alternates));
final String http_alternates = "objects/info/http-alternates";
serve("*/" + http_alternates)//
.through(mustBeLocal)//
.through(enabled)//
.with(new TextFileServlet(http_alternates));
serve("*/objects/info/packs")//
.through(mustBeLocal)//
.through(enabled)//
.with(new InfoPacksServlet());
serveRegex("^/(.*)/objects/([0-9a-f]{2}/[0-9a-f]{38})$")//
.through(mustBeLocal)//
.through(enabled)//
.through(new RegexGroupFilter(2))//
.with(new ObjectFileServlet.Loose());
serveRegex("^/(.*)/objects/(pack/pack-[0-9a-f]{40}\\.pack)$")//
.through(mustBeLocal)//
.through(enabled)//
.through(new RegexGroupFilter(2))//
.with(new ObjectFileServlet.Pack());
serveRegex("^/(.*)/objects/(pack/pack-[0-9a-f]{40}\\.idx)$")//
.through(mustBeLocal)//
.through(enabled)//
.through(new RegexGroupFilter(2))//
.with(new ObjectFileServlet.PackIdx());
}
}
private File getFile(final String param) throws ServletException {
String n = getInitParameter(param);
if (n == null || "".equals(n))
throw new ServletException(MessageFormat.format(HttpServerText.get().parameterNotSet, param));
File path = new File(n);
if (!path.exists())
throw new ServletException(MessageFormat.format(HttpServerText.get().pathForParamNotFound, path, param));
return path;
}
private boolean getBoolean(String param) throws ServletException {
String n = getInitParameter(param);
if (n == null)
return false;
try {
return StringUtils.toBoolean(n);
} catch (IllegalArgumentException err) {
throw new ServletException(MessageFormat.format(HttpServerText.get().invalidBoolean, param, n));
}
}
@Override
protected ServletBinder register(ServletBinder binder) {
if (resolver == null)
throw new IllegalStateException(HttpServerText.get().noResolverAvailable);
binder = binder.through(new NoCacheFilter());
binder = binder.through(new RepositoryFilter(resolver));
return binder;
gitFilter.init(new FilterConfig() {
public String getFilterName() {
return gitFilter.getClass().getName();
}
public String getInitParameter(String name) {
return config.getInitParameter(name);
}
public Enumeration getInitParameterNames() {
return config.getInitParameterNames();
}
public ServletContext getServletContext() {
return config.getServletContext();
}
});
}
}

222
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java

@ -0,0 +1,222 @@
/*
* Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.http.server.glue;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.http.server.HttpServerText;
/**
* Generic container filter to manage routing to different pipelines.
* <p>
* Callers can create and configure a new processing pipeline by using one of
* the {@link #serve(String)} or {@link #serveRegex(String)} methods to allocate
* a binder for a particular URL pattern.
* <p>
* Registered filters and servlets are initialized lazily, usually during the
* first request. Once initialized the bindings in this servlet cannot be
* modified without destroying the servlet and thereby destroying all registered
* filters and servlets.
*/
public class MetaFilter implements Filter {
static final String REGEX_GROUPS = "org.eclipse.jgit.http.server.glue.MetaServlet.serveRegex";
private ServletContext servletContext;
private final List<ServletBinderImpl> bindings;
private volatile UrlPipeline[] pipelines;
/** Empty filter with no bindings. */
public MetaFilter() {
this.bindings = new ArrayList<ServletBinderImpl>();
}
/**
* Construct a binding for a specific path.
*
* @param path
* pattern to match.
* @return binder for the passed path.
*/
public ServletBinder serve(String path) {
if (path.startsWith("*"))
return register(new SuffixPipeline.Binder(path.substring(1)));
throw new IllegalArgumentException(MessageFormat.format(HttpServerText
.get().pathNotSupported, path));
}
/**
* Construct a binding for a regular expression.
*
* @param expression
* the regular expression to pattern match the URL against.
* @return binder for the passed expression.
*/
public ServletBinder serveRegex(String expression) {
return register(new RegexPipeline.Binder(expression));
}
public void init(FilterConfig filterConfig) throws ServletException {
servletContext = filterConfig.getServletContext();
}
public void destroy() {
if (pipelines != null) {
Set<Object> destroyed = newIdentitySet();
for (UrlPipeline p : pipelines)
p.destroy(destroyed);
pipelines = null;
}
}
private static Set<Object> newIdentitySet() {
final Map<Object, Object> m = new IdentityHashMap<Object, Object>();
return new AbstractSet<Object>() {
@Override
public boolean add(Object o) {
return m.put(o, o) == null;
}
@Override
public boolean contains(Object o) {
return m.keySet().contains(o);
}
@Override
public Iterator<Object> iterator() {
return m.keySet().iterator();
}
@Override
public int size() {
return m.size();
}
};
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
UrlPipeline p = find(req);
if (p != null)
p.service(req, res);
else
chain.doFilter(req, res);
}
private UrlPipeline find(HttpServletRequest req) throws ServletException {
for (UrlPipeline p : getPipelines())
if (p.match(req))
return p;
return null;
}
private ServletBinder register(ServletBinderImpl b) {
synchronized (bindings) {
if (pipelines != null)
throw new IllegalStateException(
HttpServerText.get().servletAlreadyInitialized);
bindings.add(b);
}
return register((ServletBinder) b);
}
/**
* Configure a newly created binder.
*
* @param b
* the newly created binder.
* @return binder for the caller, potentially after adding one or more
* filters into the pipeline.
*/
protected ServletBinder register(ServletBinder b) {
return b;
}
private UrlPipeline[] getPipelines() throws ServletException {
UrlPipeline[] r = pipelines;
if (r == null) {
synchronized (bindings) {
r = pipelines;
if (r == null) {
r = createPipelines();
pipelines = r;
}
}
}
return r;
}
private UrlPipeline[] createPipelines() throws ServletException {
UrlPipeline[] array = new UrlPipeline[bindings.size()];
for (int i = 0; i < bindings.size(); i++)
array[i] = bindings.get(i).create();
Set<Object> inited = newIdentitySet();
for (UrlPipeline p : array)
p.init(servletContext, inited);
return array;
}
}

142
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java

@ -46,22 +46,17 @@ package org.eclipse.jgit.http.server.glue;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
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;
import org.eclipse.jgit.http.server.HttpServerText;
/**
* Generic container servlet to manage routing to different pipelines.
* <p>
@ -77,15 +72,26 @@ import org.eclipse.jgit.http.server.HttpServerText;
public class MetaServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
static final String REGEX_GROUPS = "org.eclipse.jgit.http.server.glue.MetaServlet.serveRegex";
private final List<ServletBinderImpl> bindings;
private volatile UrlPipeline[] pipelines;
private final MetaFilter filter;
/** Empty servlet with no bindings. */
public MetaServlet() {
this.bindings = new ArrayList<ServletBinderImpl>();
this(new MetaFilter());
}
/**
* Initialize a servlet wrapping a filter.
*
* @param delegateFilter
* the filter being wrapped by the servlet.
*/
protected MetaServlet(MetaFilter delegateFilter) {
filter = delegateFilter;
}
/** @return filter this servlet delegates all routing logic to. */
protected MetaFilter getDelegateFilter() {
return filter;
}
/**
@ -96,9 +102,7 @@ public class MetaServlet extends HttpServlet {
* @return binder for the passed path.
*/
public ServletBinder serve(String path) {
if (path.startsWith("*"))
return register(new SuffixPipeline.Binder(path.substring(1)));
throw new IllegalArgumentException(MessageFormat.format(HttpServerText.get().pathNotSupported, path));
return filter.serve(path);
}
/**
@ -109,68 +113,30 @@ public class MetaServlet extends HttpServlet {
* @return binder for the passed expression.
*/
public ServletBinder serveRegex(String expression) {
return register(new RegexPipeline.Binder(expression));
}
public void destroy() {
if (pipelines != null) {
Set<Object> destroyed = newIdentitySet();
for (UrlPipeline p : pipelines)
p.destroy(destroyed);
pipelines = null;
}
}
private static Set<Object> newIdentitySet() {
final Map<Object, Object> m = new IdentityHashMap<Object, Object>();
return new AbstractSet<Object>() {
@Override
public boolean add(Object o) {
return m.put(o, o) == null;
}
@Override
public boolean contains(Object o) {
return m.keySet().contains(o);
}
@Override
public Iterator<Object> iterator() {
return m.keySet().iterator();
}
@Override
public int size() {
return m.size();
}
};
return filter.serveRegex(expression);
}
@Override
protected void service(final HttpServletRequest req,
final HttpServletResponse rsp) throws ServletException, IOException {
final UrlPipeline p = find(req);
if (p != null)
p.service(req, rsp);
else
rsp.sendError(SC_NOT_FOUND);
public void init(ServletConfig config) throws ServletException {
String name = filter.getClass().getName();
ServletContext ctx = config.getServletContext();
filter.init(new NoParameterFilterConfig(name, ctx));
}
private UrlPipeline find(final HttpServletRequest req)
throws ServletException {
for (UrlPipeline p : getPipelines())
if (p.match(req))
return p;
return null;
public void destroy() {
filter.destroy();
}
private ServletBinder register(ServletBinderImpl b) {
synchronized (bindings) {
if (pipelines != null)
throw new IllegalStateException(HttpServerText.get().servletAlreadyInitialized);
bindings.add(b);
}
return register((ServletBinder) b);
@Override
protected void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
filter.doFilter(req, res, new FilterChain() {
public void doFilter(ServletRequest request,
ServletResponse response) throws IOException,
ServletException {
((HttpServletResponse) response).sendError(SC_NOT_FOUND);
}
});
}
/**
@ -182,32 +148,6 @@ public class MetaServlet extends HttpServlet {
* filters into the pipeline.
*/
protected ServletBinder register(ServletBinder b) {
return b;
}
private UrlPipeline[] getPipelines() throws ServletException {
UrlPipeline[] r = pipelines;
if (r == null) {
synchronized (bindings) {
r = pipelines;
if (r == null) {
r = createPipelines();
pipelines = r;
}
}
}
return r;
}
private UrlPipeline[] createPipelines() throws ServletException {
UrlPipeline[] array = new UrlPipeline[bindings.size()];
for (int i = 0; i < bindings.size(); i++)
array[i] = bindings.get(i).create();
Set<Object> inited = newIdentitySet();
for (UrlPipeline p : array)
p.init(getServletContext(), inited);
return array;
return filter.register(b);
}
}

85
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java

@ -0,0 +1,85 @@
/*
* Copyright (C) 2009-2010, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Distribution License v1.0 which
* accompanies this distribution, is reproduced below, and is
* available at http://www.eclipse.org/org/documents/edl-v10.php
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* - Neither the name of the Eclipse Foundation, Inc. nor the
* names of its contributors may be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.eclipse.jgit.http.server.glue;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
final class NoParameterFilterConfig implements FilterConfig {
private final String filterName;
private final ServletContext context;
NoParameterFilterConfig(String filterName, ServletContext context) {
this.filterName = filterName;
this.context = context;
}
public String getInitParameter(String name) {
return null;
}
public Enumeration getInitParameterNames() {
return new Enumeration<String>() {
public boolean hasMoreElements() {
return false;
}
public String nextElement() {
throw new NoSuchElementException();
}
};
}
public ServletContext getServletContext() {
return context;
}
public String getFilterName() {
return filterName;
}
}

2
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java

@ -95,6 +95,6 @@ public class RegexGroupFilter implements Filter {
}
private static WrappedRequest[] groupsFor(final ServletRequest r) {
return (WrappedRequest[]) r.getAttribute(MetaServlet.REGEX_GROUPS);
return (WrappedRequest[]) r.getAttribute(MetaFilter.REGEX_GROUPS);
}
}

4
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java

@ -44,7 +44,7 @@
package org.eclipse.jgit.http.server.glue;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static org.eclipse.jgit.http.server.glue.MetaServlet.REGEX_GROUPS;
import static org.eclipse.jgit.http.server.glue.MetaFilter.REGEX_GROUPS;
import java.io.IOException;
import java.util.regex.Matcher;
@ -64,7 +64,7 @@ import javax.servlet.http.HttpServletResponse;
* <p>
* If there are capture groups in the regular expression, the matched ranges of
* the capture groups are stored as an array of modified HttpServetRequests,
* into the request attribute {@link MetaServlet#REGEX_GROUPS}.
* into the request attribute {@link MetaFilter#REGEX_GROUPS}.
* <p>
* Each servlet request has been altered to have its {@code getPathInfo()}
* method return the matched text of the corresponding capture group. A

28
org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java

@ -50,7 +50,6 @@ import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@ -111,31 +110,8 @@ abstract class UrlPipeline {
final ServletContext context, final Set<Object> inited)
throws ServletException {
if (!inited.contains(ref)) {
ref.init(new FilterConfig() {
public String getInitParameter(String name) {
return null;
}
public Enumeration getInitParameterNames() {
return new Enumeration<String>() {
public boolean hasMoreElements() {
return false;
}
public String nextElement() {
throw new NoSuchElementException();
}
};
}
public ServletContext getServletContext() {
return context;
}
public String getFilterName() {
return ref.getClass().getName();
}
});
ref.init(new NoParameterFilterConfig(ref.getClass().getName(),
context));
inited.add(ref);
}
}

Loading…
Cancel
Save