Browse Source

Refactor HTTP server stack to use Filter as base

All Git URLs operate off a suffix approach, for example the default
binding is for paths such as:

  */info/refs
  */git-upload-pack
  */git-receive-pack

These names are not common on project hosting servers, especially
one like Gerrit Code Review.

In addition to offering Git-over-HTTP as a servlet, offer it as a
filter that triggers when a matching suffix appears, but otherwise
delegates the request through the chain.  This filter would permit
Gerrit Code Review to place projects at the root of the server,
rather than within the "/p/" subdirectory, making the HTTP and SSH
URL structure exactly match each other.

To prevent breakage with existing users, the MetaServlet and
GitServlet are kept as wrappers delegating to their filters,
returning 404 Not Found when the filter has no match.

Change-Id: I2465c15c086497e0faaae5941159d80c028fa8b1
stable-1.2
Shawn O. Pearce 13 years ago
parent
commit
c3554ac583
  1. 308
      org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java
  2. 176
      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. 138
      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;
}
}

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

@ -43,32 +43,22 @@
package org.eclipse.jgit.http.server; package org.eclipse.jgit.http.server;
import java.io.File; import java.util.Enumeration;
import java.text.MessageFormat;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; 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.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.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.ReceivePack;
import org.eclipse.jgit.transport.UploadPack; 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.ReceivePackFactory;
import org.eclipse.jgit.transport.resolver.RepositoryResolver; import org.eclipse.jgit.transport.resolver.RepositoryResolver;
import org.eclipse.jgit.transport.resolver.UploadPackFactory; import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import org.eclipse.jgit.util.StringUtils;
/** /**
* Handles Git repository access over HTTP. * Handles Git repository access over HTTP.
@ -107,19 +97,7 @@ import org.eclipse.jgit.util.StringUtils;
public class GitServlet extends MetaServlet { public class GitServlet extends MetaServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private volatile boolean initialized; private final GitFilter gitFilter;
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}. * 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. * the local filesystem directory where all served Git repositories reside.
*/ */
public GitServlet() { 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. * {@code web.xml} file of the web application.
*/ */
public void setRepositoryResolver(RepositoryResolver<HttpServletRequest> resolver) { public void setRepositoryResolver(RepositoryResolver<HttpServletRequest> resolver) {
assertNotInitialized(); gitFilter.setRepositoryResolver(resolver);
this.resolver = resolver;
} }
/** /**
@ -152,8 +130,7 @@ public class GitServlet extends MetaServlet {
* support is completely disabled. * support is completely disabled.
*/ */
public void setAsIsFileService(AsIsFileService f) { public void setAsIsFileService(AsIsFileService f) {
assertNotInitialized(); gitFilter.setAsIsFileService(f);
this.asIs = f != null ? f : AsIsFileService.DISABLED;
} }
/** /**
@ -163,8 +140,7 @@ public class GitServlet extends MetaServlet {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void setUploadPackFactory(UploadPackFactory<HttpServletRequest> f) { public void setUploadPackFactory(UploadPackFactory<HttpServletRequest> f) {
assertNotInitialized(); gitFilter.setUploadPackFactory(f);
this.uploadPackFactory = f != null ? f : (UploadPackFactory<HttpServletRequest>)UploadPackFactory.DISABLED;
} }
/** /**
@ -174,8 +150,7 @@ public class GitServlet extends MetaServlet {
* {@link ServletUtils#ATTRIBUTE_HANDLER}. * {@link ServletUtils#ATTRIBUTE_HANDLER}.
*/ */
public void addUploadPackFilter(Filter filter) { public void addUploadPackFilter(Filter filter) {
assertNotInitialized(); gitFilter.addUploadPackFilter(filter);
uploadPackFilters.add(filter);
} }
/** /**
@ -185,8 +160,7 @@ public class GitServlet extends MetaServlet {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void setReceivePackFactory(ReceivePackFactory<HttpServletRequest> f) { public void setReceivePackFactory(ReceivePackFactory<HttpServletRequest> f) {
assertNotInitialized(); gitFilter.setReceivePackFactory(f);
this.receivePackFactory = f != null ? f : (ReceivePackFactory<HttpServletRequest>)ReceivePackFactory.DISABLED;
} }
/** /**
@ -196,133 +170,27 @@ public class GitServlet extends MetaServlet {
* {@link ServletUtils#ATTRIBUTE_HANDLER}. * {@link ServletUtils#ATTRIBUTE_HANDLER}.
*/ */
public void addReceivePackFilter(Filter filter) { public void addReceivePackFilter(Filter filter) {
assertNotInitialized(); gitFilter.addReceivePackFilter(filter);
receivePackFilters.add(filter);
}
private void assertNotInitialized() {
if (initialized)
throw new IllegalStateException(HttpServerText.get().alreadyInitializedByContainer);
} }
@Override @Override
public void init(final ServletConfig config) throws ServletException { public void init(final ServletConfig config) throws ServletException {
super.init(config); gitFilter.init(new FilterConfig() {
public String getFilterName() {
if (resolver == null) { return gitFilter.getClass().getName();
final File root = getFile("base-path");
final boolean exportAll = getBoolean("export-all");
setRepositoryResolver(new FileResolver<HttpServletRequest>(root, exportAll));
} }
initialized = true; public String getInitParameter(String name) {
return config.getInitParameter(name);
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) { public Enumeration getInitParameterNames() {
ServletBinder b = serve("*/git-receive-pack"); return config.getInitParameterNames();
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); public ServletContext getServletContext() {
if (uploadPackFactory != UploadPackFactory.DISABLED) { return config.getServletContext();
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;
} }
} }

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;
}
}

138
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 static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import java.io.IOException; 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.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.http.server.HttpServerText;
/** /**
* Generic container servlet to manage routing to different pipelines. * Generic container servlet to manage routing to different pipelines.
* <p> * <p>
@ -77,15 +72,26 @@ import org.eclipse.jgit.http.server.HttpServerText;
public class MetaServlet extends HttpServlet { public class MetaServlet extends HttpServlet {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
static final String REGEX_GROUPS = "org.eclipse.jgit.http.server.glue.MetaServlet.serveRegex"; private final MetaFilter filter;
private final List<ServletBinderImpl> bindings;
private volatile UrlPipeline[] pipelines;
/** Empty servlet with no bindings. */ /** Empty servlet with no bindings. */
public MetaServlet() { 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. * @return binder for the passed path.
*/ */
public ServletBinder serve(String path) { public ServletBinder serve(String path) {
if (path.startsWith("*")) return filter.serve(path);
return register(new SuffixPipeline.Binder(path.substring(1)));
throw new IllegalArgumentException(MessageFormat.format(HttpServerText.get().pathNotSupported, path));
} }
/** /**
@ -109,68 +113,30 @@ public class MetaServlet extends HttpServlet {
* @return binder for the passed expression. * @return binder for the passed expression.
*/ */
public ServletBinder serveRegex(String expression) { public ServletBinder serveRegex(String expression) {
return register(new RegexPipeline.Binder(expression)); return filter.serveRegex(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 @Override
public Iterator<Object> iterator() { public void init(ServletConfig config) throws ServletException {
return m.keySet().iterator(); String name = filter.getClass().getName();
ServletContext ctx = config.getServletContext();
filter.init(new NoParameterFilterConfig(name, ctx));
} }
@Override public void destroy() {
public int size() { filter.destroy();
return m.size();
}
};
} }
@Override @Override
protected void service(final HttpServletRequest req, protected void service(HttpServletRequest req, HttpServletResponse res)
final HttpServletResponse rsp) throws ServletException, IOException { throws ServletException, IOException {
final UrlPipeline p = find(req); filter.doFilter(req, res, new FilterChain() {
if (p != null) public void doFilter(ServletRequest request,
p.service(req, rsp); ServletResponse response) throws IOException,
else ServletException {
rsp.sendError(SC_NOT_FOUND); ((HttpServletResponse) response).sendError(SC_NOT_FOUND);
}
private UrlPipeline find(final 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); });
} }
/** /**
@ -182,32 +148,6 @@ public class MetaServlet extends HttpServlet {
* filters into the pipeline. * filters into the pipeline.
*/ */
protected ServletBinder register(ServletBinder b) { protected ServletBinder register(ServletBinder b) {
return b; return filter.register(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;
} }
} }

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) { 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; package org.eclipse.jgit.http.server.glue;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; 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.io.IOException;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -64,7 +64,7 @@ import javax.servlet.http.HttpServletResponse;
* <p> * <p>
* If there are capture groups in the regular expression, the matched ranges of * If there are capture groups in the regular expression, the matched ranges of
* the capture groups are stored as an array of modified HttpServetRequests, * 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> * <p>
* Each servlet request has been altered to have its {@code getPathInfo()} * Each servlet request has been altered to have its {@code getPathInfo()}
* method return the matched text of the corresponding capture group. A * 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.Filter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig; import javax.servlet.ServletConfig;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -111,31 +110,8 @@ abstract class UrlPipeline {
final ServletContext context, final Set<Object> inited) final ServletContext context, final Set<Object> inited)
throws ServletException { throws ServletException {
if (!inited.contains(ref)) { if (!inited.contains(ref)) {
ref.init(new FilterConfig() { ref.init(new NoParameterFilterConfig(ref.getClass().getName(),
public String getInitParameter(String name) { context));
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();
}
});
inited.add(ref); inited.add(ref);
} }
} }

Loading…
Cancel
Save