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 000000000..7b88ae346 --- /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. + *

+ * Applications embedding this filter should map a directory path within the + * application to this filter. For a servlet version, see {@link GitServlet}. + *

+ * 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 resolver; + + private AsIsFileService asIs = new AsIsFileService(); + + private UploadPackFactory uploadPackFactory = new DefaultUploadPackFactory(); + + private ReceivePackFactory receivePackFactory = new DefaultReceivePackFactory(); + + private final List uploadPackFilters = new LinkedList(); + + private final List receivePackFilters = new LinkedList(); + + /** + * New servlet that will load its base directory from {@code web.xml}. + *

+ * 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 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 f) { + assertNotInitialized(); + this.uploadPackFactory = f != null ? f : (UploadPackFactory)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 f) { + assertNotInitialized(); + this.receivePackFactory = f != null ? f : (ReceivePackFactory)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(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 caa84e46b..ef3dc2cc7 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 @@ public class GitServlet extends MetaServlet { private static final long serialVersionUID = 1L; - private volatile boolean initialized; - - private RepositoryResolver resolver; - - private AsIsFileService asIs = new AsIsFileService(); - - private UploadPackFactory uploadPackFactory = new DefaultUploadPackFactory(); - - private ReceivePackFactory receivePackFactory = new DefaultReceivePackFactory(); - - private final List uploadPackFilters = new LinkedList(); - - private final List receivePackFilters = new LinkedList(); + 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 GitServlet() { * {@code web.xml} file of the web application. */ public void setRepositoryResolver(RepositoryResolver resolver) { - assertNotInitialized(); - this.resolver = resolver; + gitFilter.setRepositoryResolver(resolver); } /** @@ -152,8 +130,7 @@ public void setRepositoryResolver(RepositoryResolver resolve * 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 void setAsIsFileService(AsIsFileService f) { */ @SuppressWarnings("unchecked") public void setUploadPackFactory(UploadPackFactory f) { - assertNotInitialized(); - this.uploadPackFactory = f != null ? f : (UploadPackFactory)UploadPackFactory.DISABLED; + gitFilter.setUploadPackFactory(f); } /** @@ -174,8 +150,7 @@ public void setUploadPackFactory(UploadPackFactory f) { * {@link ServletUtils#ATTRIBUTE_HANDLER}. */ public void addUploadPackFilter(Filter filter) { - assertNotInitialized(); - uploadPackFilters.add(filter); + gitFilter.addUploadPackFilter(filter); } /** @@ -185,8 +160,7 @@ public void addUploadPackFilter(Filter filter) { */ @SuppressWarnings("unchecked") public void setReceivePackFactory(ReceivePackFactory f) { - assertNotInitialized(); - this.receivePackFactory = f != null ? f : (ReceivePackFactory)ReceivePackFactory.DISABLED; + gitFilter.setReceivePackFactory(f); } /** @@ -196,133 +170,27 @@ public void setReceivePackFactory(ReceivePackFactory f) { * {@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); + gitFilter.init(new FilterConfig() { + public String getFilterName() { + return gitFilter.getClass().getName(); + } - if (resolver == null) { - final File root = getFile("base-path"); - final boolean exportAll = getBoolean("export-all"); - setRepositoryResolver(new FileResolver(root, exportAll)); - } + public String getInitParameter(String name) { + return config.getInitParameter(name); + } - initialized = true; + public Enumeration getInitParameterNames() { + return config.getInitParameterNames(); + } - 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; + 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 000000000..8cb3bea1f --- /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. + *

+ * 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. + *

+ * 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 bindings; + + private volatile UrlPipeline[] pipelines; + + /** Empty filter with no bindings. */ + public MetaFilter() { + this.bindings = new ArrayList(); + } + + /** + * 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 destroyed = newIdentitySet(); + for (UrlPipeline p : pipelines) + p.destroy(destroyed); + pipelines = null; + } + } + + private static Set newIdentitySet() { + final Map m = new IdentityHashMap(); + return new AbstractSet() { + @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 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 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 7764a90c1..05060658d 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 @@ 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. *

@@ -77,15 +72,26 @@ 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 bindings; - - private volatile UrlPipeline[] pipelines; + private final MetaFilter filter; /** Empty servlet with no bindings. */ public MetaServlet() { - this.bindings = new ArrayList(); + 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 MetaServlet() { * @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 ServletBinder serve(String path) { * @return binder for the passed expression. */ public ServletBinder serveRegex(String expression) { - return register(new RegexPipeline.Binder(expression)); - } - - public void destroy() { - if (pipelines != null) { - Set destroyed = newIdentitySet(); - for (UrlPipeline p : pipelines) - p.destroy(destroyed); - pipelines = null; - } - } - - private static Set newIdentitySet() { - final Map m = new IdentityHashMap(); - return new AbstractSet() { - @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 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 @@ private ServletBinder register(ServletBinderImpl b) { * 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 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 000000000..c7ca2b289 --- /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() { + 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 5c7b965ff..bee2bfc19 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 void doFilter(final ServletRequest request, } 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 635ff5493..a4acd1550 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 @@ *

* 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}. *

* 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 2257966d6..53008deb1 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 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 @@ private static void initFilter(final Filter ref, final ServletContext context, final Set inited) throws ServletException { if (!inited.contains(ref)) { - ref.init(new FilterConfig() { - public String getInitParameter(String name) { - return null; - } - - public Enumeration getInitParameterNames() { - return new Enumeration() { - 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); } }