diff options
author | Shawn O. Pearce <spearce@spearce.org> | 2011-10-07 17:23:58 -0700 |
---|---|---|
committer | Shawn O. Pearce <spearce@spearce.org> | 2011-10-07 17:23:58 -0700 |
commit | c3554ac583c4c4e09264a61495b83c58c1740180 (patch) | |
tree | 97fb12c7006d46d396d5f97e8d90a1241fa0567d /org.eclipse.jgit.http.server/src/org/eclipse | |
parent | cc03e27093c3b24afe2dbe3f64138efcb659ba76 (diff) | |
download | jgit-c3554ac583c4c4e09264a61495b83c58c1740180.tar.gz jgit-c3554ac583c4c4e09264a61495b83c58c1740180.zip |
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
Diffstat (limited to 'org.eclipse.jgit.http.server/src/org/eclipse')
8 files changed, 690 insertions, 291 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitFilter.java new file mode 100644 index 0000000000..7b88ae346a --- /dev/null +++ b/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; + } +} diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java index caa84e46ba..ef3dc2cc73 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java +++ b/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(); + } + }); } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaFilter.java new file mode 100644 index 0000000000..8cb3bea1f9 --- /dev/null +++ b/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; + } +} diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java index 7764a90c12..05060658d6 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java +++ b/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); } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/NoParameterFilterConfig.java new file mode 100644 index 0000000000..c7ca2b2898 --- /dev/null +++ b/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; + } +} diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java index 5c7b965ff7..bee2bfc191 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java +++ b/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); } } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java index 635ff5493f..a4acd15508 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java +++ b/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 diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java index 2257966d69..53008deb11 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java +++ b/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); } } |