Add support for HTTP plugins
Plugins may contribute to the /plugins/NAME/ URL space by providing
a ServletModule in the manifest using Gerrit-HttpModule and binding
servlets and filters using Guice bindings.
All names are relative to the plugin's directory, so
  serve("/").with(IndexServlet.class);
will handle /plugins/NAME/ and not "/" on the server. This makes a
plugin automatically relocatable to match its SSH command name or
the name in $site_dir/plugins.
Change-Id: I17e3007f0310d2bf4989d652f18864a77c5d5f2e
			
			
This commit is contained in:
		
				
					committed by
					
						
						gerrit code review
					
				
			
			
				
	
			
			
			
						parent
						
							9c11044bf7
						
					
				
				
					commit
					2aefecf604
				
			@@ -0,0 +1,30 @@
 | 
			
		||||
// Copyright (C) 2012 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.plugins;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.server.plugins.StartPluginListener;
 | 
			
		||||
import com.google.inject.internal.UniqueAnnotations;
 | 
			
		||||
import com.google.inject.servlet.ServletModule;
 | 
			
		||||
 | 
			
		||||
public class HttpPluginModule extends ServletModule {
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void configureServlets() {
 | 
			
		||||
    serve("/plugins/*").with(HttpPluginServlet.class);
 | 
			
		||||
 | 
			
		||||
    bind(StartPluginListener.class)
 | 
			
		||||
      .annotatedWith(UniqueAnnotations.create())
 | 
			
		||||
      .to(HttpPluginServlet.class);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,166 @@
 | 
			
		||||
// Copyright (C) 2012 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.plugins;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
import com.google.gerrit.server.plugins.Plugin;
 | 
			
		||||
import com.google.gerrit.server.plugins.RegistrationHandle;
 | 
			
		||||
import com.google.gerrit.server.plugins.StartPluginListener;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
import com.google.inject.servlet.GuiceFilter;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.ConcurrentMap;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.FilterChain;
 | 
			
		||||
import javax.servlet.ServletConfig;
 | 
			
		||||
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.HttpServletRequestWrapper;
 | 
			
		||||
import javax.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
class HttpPluginServlet extends HttpServlet
 | 
			
		||||
    implements StartPluginListener {
 | 
			
		||||
  private static final long serialVersionUID = 1L;
 | 
			
		||||
  private static final Logger log
 | 
			
		||||
      = LoggerFactory.getLogger(HttpPluginServlet.class);
 | 
			
		||||
 | 
			
		||||
  private List<Plugin> pending = Lists.newArrayList();
 | 
			
		||||
  private String base;
 | 
			
		||||
  private final ConcurrentMap<String, GuiceFilter> plugins
 | 
			
		||||
      = Maps.newConcurrentMap();
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public synchronized void init(ServletConfig config) throws ServletException {
 | 
			
		||||
    super.init(config);
 | 
			
		||||
 | 
			
		||||
    String path = config.getServletContext().getContextPath();
 | 
			
		||||
    base = Strings.nullToEmpty(path) + "/plugins/";
 | 
			
		||||
    for (Plugin plugin : pending) {
 | 
			
		||||
      start(plugin);
 | 
			
		||||
    }
 | 
			
		||||
    pending = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public synchronized void onStartPlugin(Plugin plugin) {
 | 
			
		||||
    if (pending != null) {
 | 
			
		||||
      pending.add(plugin);
 | 
			
		||||
    } else {
 | 
			
		||||
      start(plugin);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void start(Plugin plugin) {
 | 
			
		||||
    if (plugin.getHttpInjector() != null) {
 | 
			
		||||
      final String name = plugin.getName();
 | 
			
		||||
      final GuiceFilter filter;
 | 
			
		||||
      try {
 | 
			
		||||
        filter = plugin.getHttpInjector().getInstance(GuiceFilter.class);
 | 
			
		||||
      } catch (RuntimeException e) {
 | 
			
		||||
        log.warn(String.format("Plugin %s cannot load GuiceFilter", name), e);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        WrappedContext ctx = new WrappedContext(plugin, base + name);
 | 
			
		||||
        filter.init(new WrappedFilterConfig(ctx));
 | 
			
		||||
      } catch (ServletException e) {
 | 
			
		||||
        log.warn(String.format("Plugin %s failed to initialize HTTP", name), e);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      plugin.add(new RegistrationHandle() {
 | 
			
		||||
        @Override
 | 
			
		||||
        public void remove() {
 | 
			
		||||
          try {
 | 
			
		||||
            filter.destroy();
 | 
			
		||||
          } finally {
 | 
			
		||||
            plugins.remove(name, filter);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      plugins.put(name, filter);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void service(HttpServletRequest req, HttpServletResponse res)
 | 
			
		||||
      throws IOException, ServletException {
 | 
			
		||||
    String name = extractName(req);
 | 
			
		||||
    GuiceFilter filter = plugins.get(name);
 | 
			
		||||
    if (filter == null) {
 | 
			
		||||
      noCache(res);
 | 
			
		||||
      res.sendError(HttpServletResponse.SC_NOT_FOUND);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    filter.doFilter(new WrappedRequest(req, base + name), res,
 | 
			
		||||
        new FilterChain() {
 | 
			
		||||
          @Override
 | 
			
		||||
          public void doFilter(ServletRequest req, ServletResponse response)
 | 
			
		||||
              throws IOException, ServletException {
 | 
			
		||||
            HttpServletResponse res = (HttpServletResponse) response;
 | 
			
		||||
            noCache(res);
 | 
			
		||||
            res.sendError(HttpServletResponse.SC_NOT_FOUND);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static String extractName(HttpServletRequest req) {
 | 
			
		||||
    String path = req.getPathInfo();
 | 
			
		||||
    if (Strings.isNullOrEmpty(path) || "/".equals(path)) {
 | 
			
		||||
      return "";
 | 
			
		||||
    }
 | 
			
		||||
    int s = path.indexOf('/', 1);
 | 
			
		||||
    return 0 <= s ? path.substring(1, s) : path.substring(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void noCache(HttpServletResponse res) {
 | 
			
		||||
    res.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
 | 
			
		||||
    res.setHeader("Pragma", "no-cache");
 | 
			
		||||
    res.setHeader("Cache-Control", "no-cache, must-revalidate");
 | 
			
		||||
    res.setHeader("Content-Disposition", "attachment");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class WrappedRequest extends HttpServletRequestWrapper {
 | 
			
		||||
    private final String contextPath;
 | 
			
		||||
 | 
			
		||||
    WrappedRequest(HttpServletRequest req, String contextPath) {
 | 
			
		||||
      super(req);
 | 
			
		||||
      this.contextPath = contextPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getContextPath() {
 | 
			
		||||
      return contextPath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getServletPath() {
 | 
			
		||||
      return ((HttpServletRequest) getRequest()).getRequestURI();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,178 @@
 | 
			
		||||
// Copyright (C) 2012 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.plugins;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Maps;
 | 
			
		||||
import com.google.gerrit.common.Version;
 | 
			
		||||
import com.google.gerrit.server.plugins.Plugin;
 | 
			
		||||
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Enumeration;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.concurrent.ConcurrentMap;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.RequestDispatcher;
 | 
			
		||||
import javax.servlet.Servlet;
 | 
			
		||||
import javax.servlet.ServletContext;
 | 
			
		||||
import javax.servlet.ServletException;
 | 
			
		||||
 | 
			
		||||
class WrappedContext implements ServletContext {
 | 
			
		||||
  private static final Logger log = LoggerFactory.getLogger("plugin");
 | 
			
		||||
  private final Plugin plugin;
 | 
			
		||||
  private final String contextPath;
 | 
			
		||||
  private final ConcurrentMap<String, Object> attributes;
 | 
			
		||||
 | 
			
		||||
  WrappedContext(Plugin plugin, String contextPath) {
 | 
			
		||||
    this.plugin = plugin;
 | 
			
		||||
    this.contextPath = contextPath;
 | 
			
		||||
    this.attributes = Maps.newConcurrentMap();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getContextPath() {
 | 
			
		||||
    return contextPath;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getInitParameter(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("rawtypes")
 | 
			
		||||
  @Override
 | 
			
		||||
  public Enumeration getInitParameterNames() {
 | 
			
		||||
    return Collections.enumeration(Collections.emptyList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public ServletContext getContext(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public RequestDispatcher getNamedDispatcher(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public RequestDispatcher getRequestDispatcher(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public URL getResource(String name) throws MalformedURLException {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public InputStream getResourceAsStream(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("rawtypes")
 | 
			
		||||
  @Override
 | 
			
		||||
  public Set getResourcePaths(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Servlet getServlet(String name) throws ServletException {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getRealPath(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getServletContextName() {
 | 
			
		||||
    return plugin.getName();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("rawtypes")
 | 
			
		||||
  @Override
 | 
			
		||||
  public Enumeration getServletNames() {
 | 
			
		||||
    return Collections.enumeration(Collections.emptyList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("rawtypes")
 | 
			
		||||
  @Override
 | 
			
		||||
  public Enumeration getServlets() {
 | 
			
		||||
    return Collections.enumeration(Collections.emptyList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void log(Exception reason, String msg) {
 | 
			
		||||
    log(msg, reason);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void log(String msg) {
 | 
			
		||||
    log(msg, null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void log(String msg, Throwable reason) {
 | 
			
		||||
    log.warn(String.format("[plugin %s] %s", plugin.getName(), msg), reason);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Object getAttribute(String name) {
 | 
			
		||||
    return attributes.get(name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Enumeration<String> getAttributeNames() {
 | 
			
		||||
    return Collections.enumeration(attributes.keySet());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void setAttribute(String name, Object value) {
 | 
			
		||||
    attributes.put(name, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void removeAttribute(String name) {
 | 
			
		||||
    attributes.remove(name);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getMimeType(String file) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public int getMajorVersion() {
 | 
			
		||||
    return 2;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public int getMinorVersion() {
 | 
			
		||||
    return 5;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getServerInfo() {
 | 
			
		||||
    String v = Version.getVersion();
 | 
			
		||||
    return "Gerrit Code Review/" + (v != null ? v : "dev");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,52 @@
 | 
			
		||||
// Copyright (C) 2012 The Android Open Source Project
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
// http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.httpd.plugins;
 | 
			
		||||
 | 
			
		||||
import com.google.inject.servlet.GuiceFilter;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Enumeration;
 | 
			
		||||
 | 
			
		||||
import javax.servlet.FilterConfig;
 | 
			
		||||
import javax.servlet.ServletContext;
 | 
			
		||||
 | 
			
		||||
class WrappedFilterConfig implements FilterConfig {
 | 
			
		||||
  private final WrappedContext context;
 | 
			
		||||
 | 
			
		||||
  WrappedFilterConfig(WrappedContext context) {
 | 
			
		||||
    this.context = context;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getFilterName() {
 | 
			
		||||
    return GuiceFilter.class.getName();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getInitParameter(String name) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("rawtypes")
 | 
			
		||||
  @Override
 | 
			
		||||
  public Enumeration getInitParameterNames() {
 | 
			
		||||
    return Collections.enumeration(Collections.emptyList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public ServletContext getServletContext() {
 | 
			
		||||
    return context;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -24,6 +24,7 @@ import com.google.gerrit.httpd.HttpCanonicalWebUrlProvider;
 | 
			
		||||
import com.google.gerrit.httpd.WebModule;
 | 
			
		||||
import com.google.gerrit.httpd.WebSshGlueModule;
 | 
			
		||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 | 
			
		||||
import com.google.gerrit.httpd.plugins.HttpPluginModule;
 | 
			
		||||
import com.google.gerrit.lifecycle.LifecycleManager;
 | 
			
		||||
import com.google.gerrit.pgm.http.jetty.GetUserFilter;
 | 
			
		||||
import com.google.gerrit.pgm.http.jetty.JettyEnv;
 | 
			
		||||
@@ -259,6 +260,9 @@ public class Daemon extends SiteProgram {
 | 
			
		||||
  private void initHttpd() {
 | 
			
		||||
    webInjector = createWebInjector();
 | 
			
		||||
 | 
			
		||||
    sysInjector.getInstance(PluginGuiceEnvironment.class)
 | 
			
		||||
        .setHttpInjector(webInjector);
 | 
			
		||||
 | 
			
		||||
    sysInjector.getInstance(HttpCanonicalWebUrlProvider.class)
 | 
			
		||||
        .setHttpServletRequest(
 | 
			
		||||
            webInjector.getProvider(HttpServletRequest.class));
 | 
			
		||||
@@ -273,6 +277,7 @@ public class Daemon extends SiteProgram {
 | 
			
		||||
    modules.add(HttpContactStoreConnection.module());
 | 
			
		||||
    modules.add(sysInjector.getInstance(GitOverHttpModule.class));
 | 
			
		||||
    modules.add(sysInjector.getInstance(WebModule.class));
 | 
			
		||||
    modules.add(new HttpPluginModule());
 | 
			
		||||
    if (sshd) {
 | 
			
		||||
      modules.add(sshInjector.getInstance(WebSshGlueModule.class));
 | 
			
		||||
      modules.add(new ProjectQoSFilter.Module());
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,7 @@ import org.eclipse.jetty.server.handler.RequestLogHandler;
 | 
			
		||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
 | 
			
		||||
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
 | 
			
		||||
import org.eclipse.jetty.servlet.DefaultServlet;
 | 
			
		||||
import org.eclipse.jetty.servlet.FilterHolder;
 | 
			
		||||
import org.eclipse.jetty.servlet.FilterMapping;
 | 
			
		||||
import org.eclipse.jetty.servlet.ServletContextHandler;
 | 
			
		||||
import org.eclipse.jetty.servlet.ServletHolder;
 | 
			
		||||
@@ -328,7 +329,8 @@ public class JettyServer {
 | 
			
		||||
    // of using the listener to create the injector pass the one we
 | 
			
		||||
    // already have built.
 | 
			
		||||
    //
 | 
			
		||||
    app.addFilter(GuiceFilter.class, "/*", FilterMapping.DEFAULT);
 | 
			
		||||
    GuiceFilter filter = env.webInjector.getInstance(GuiceFilter.class);
 | 
			
		||||
    app.addFilter(new FilterHolder(filter), "/*", FilterMapping.DEFAULT);
 | 
			
		||||
    app.addEventListener(new GuiceServletContextListener() {
 | 
			
		||||
      @Override
 | 
			
		||||
      protected Injector getInjector() {
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,17 @@ limitations under the License.
 | 
			
		||||
      <artifactId>gerrit-sshd</artifactId>
 | 
			
		||||
      <version>${project.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>com.google.gerrit</groupId>
 | 
			
		||||
      <artifactId>gerrit-httpd</artifactId>
 | 
			
		||||
      <version>${project.version}</version>
 | 
			
		||||
    </dependency>
 | 
			
		||||
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.apache.tomcat</groupId>
 | 
			
		||||
      <artifactId>servlet-api</artifactId>
 | 
			
		||||
    </dependency>
 | 
			
		||||
  </dependencies>
 | 
			
		||||
 | 
			
		||||
  <build>
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import com.google.inject.AbstractModule;
 | 
			
		||||
import com.google.inject.Guice;
 | 
			
		||||
import com.google.inject.Injector;
 | 
			
		||||
import com.google.inject.Module;
 | 
			
		||||
import com.google.inject.servlet.GuiceFilter;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.storage.file.FileSnapshot;
 | 
			
		||||
 | 
			
		||||
@@ -30,15 +31,24 @@ import java.util.jar.Manifest;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
 | 
			
		||||
public class Plugin {
 | 
			
		||||
  static {
 | 
			
		||||
    // Guice logs warnings about multiple injectors being created.
 | 
			
		||||
    // Silence this in case HTTP plugins are used.
 | 
			
		||||
    java.util.logging.Logger.getLogger(GuiceFilter.class.getName())
 | 
			
		||||
        .setLevel(java.util.logging.Level.OFF);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private final String name;
 | 
			
		||||
  private final File jar;
 | 
			
		||||
  private final Manifest manifest;
 | 
			
		||||
  private final FileSnapshot snapshot;
 | 
			
		||||
  private Class<? extends Module> sysModule;
 | 
			
		||||
  private Class<? extends Module> sshModule;
 | 
			
		||||
  private Class<? extends Module> httpModule;
 | 
			
		||||
 | 
			
		||||
  private Injector sysInjector;
 | 
			
		||||
  private Injector sshInjector;
 | 
			
		||||
  private Injector httpInjector;
 | 
			
		||||
  private LifecycleManager manager;
 | 
			
		||||
 | 
			
		||||
  public Plugin(String name,
 | 
			
		||||
@@ -46,13 +56,15 @@ public class Plugin {
 | 
			
		||||
      Manifest manifest,
 | 
			
		||||
      FileSnapshot snapshot,
 | 
			
		||||
      @Nullable Class<? extends Module> sysModule,
 | 
			
		||||
      @Nullable Class<? extends Module> sshModule) {
 | 
			
		||||
      @Nullable Class<? extends Module> sshModule,
 | 
			
		||||
      @Nullable Class<? extends Module> httpModule) {
 | 
			
		||||
    this.name = name;
 | 
			
		||||
    this.jar = jar;
 | 
			
		||||
    this.manifest = manifest;
 | 
			
		||||
    this.snapshot = snapshot;
 | 
			
		||||
    this.sysModule = sysModule;
 | 
			
		||||
    this.sshModule = sshModule;
 | 
			
		||||
    this.httpModule = httpModule;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  File getJar() {
 | 
			
		||||
@@ -90,6 +102,13 @@ public class Plugin {
 | 
			
		||||
      manager.add(sshInjector);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (httpModule != null && env.hasHttpModule()) {
 | 
			
		||||
      httpInjector = sysInjector.createChildInjector(
 | 
			
		||||
          env.getHttpModule(),
 | 
			
		||||
          sysInjector.getInstance(httpModule));
 | 
			
		||||
      manager.add(httpInjector);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    manager.start();
 | 
			
		||||
    env.onStartPlugin(this);
 | 
			
		||||
  }
 | 
			
		||||
@@ -113,6 +132,7 @@ public class Plugin {
 | 
			
		||||
      manager = null;
 | 
			
		||||
      sysInjector = null;
 | 
			
		||||
      sshInjector = null;
 | 
			
		||||
      httpInjector = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -121,6 +141,11 @@ public class Plugin {
 | 
			
		||||
    return sshInjector;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Nullable
 | 
			
		||||
  public Injector getHttpInjector() {
 | 
			
		||||
    return httpInjector;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void add(final RegistrationHandle handle) {
 | 
			
		||||
    add(new LifecycleListener() {
 | 
			
		||||
      @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ public class PluginGuiceEnvironment {
 | 
			
		||||
  private final List<StartPluginListener> listeners;
 | 
			
		||||
  private Module sysModule;
 | 
			
		||||
  private Module sshModule;
 | 
			
		||||
  private Module httpModule;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
  PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
 | 
			
		||||
@@ -84,6 +85,19 @@ public class PluginGuiceEnvironment {
 | 
			
		||||
    return sshModule;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void setHttpInjector(Injector httpInjector) {
 | 
			
		||||
    httpModule = copy(httpInjector);
 | 
			
		||||
    listeners.addAll(getListeners(httpInjector));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  boolean hasHttpModule() {
 | 
			
		||||
    return httpModule != null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Module getHttpModule() {
 | 
			
		||||
    return httpModule;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void onStartPlugin(Plugin plugin) {
 | 
			
		||||
    for (StartPluginListener l : listeners) {
 | 
			
		||||
      l.onStartPlugin(plugin);
 | 
			
		||||
@@ -126,15 +140,74 @@ public class PluginGuiceEnvironment {
 | 
			
		||||
 | 
			
		||||
  private static boolean shouldCopy(Key<?> key) {
 | 
			
		||||
    Class<?> type = key.getTypeLiteral().getRawType();
 | 
			
		||||
    if (type == LifecycleListener.class) {
 | 
			
		||||
    if (LifecycleListener.class.isAssignableFrom(type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (type == StartPluginListener.class) {
 | 
			
		||||
    if (StartPluginListener.class.isAssignableFrom(type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if ("org.apache.sshd.server.Command".equals(type.getName())) {
 | 
			
		||||
 | 
			
		||||
    if (type.getName().startsWith("com.google.inject.")) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is("org.apache.sshd.server.Command", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is("javax.servlet.Filter", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.ServletContext", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.ServletRequest", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.ServletResponse", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.http.HttpServlet", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.http.HttpServletRequest", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.http.HttpServletResponse", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (is("javax.servlet.http.HttpSession", type)) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (Map.class.isAssignableFrom(type)
 | 
			
		||||
        && key.getAnnotationType() != null
 | 
			
		||||
        && "com.google.inject.servlet.RequestParameters"
 | 
			
		||||
            .equals(key.getAnnotationType().getName())) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (type.getName().startsWith("com.google.gerrit.httpd.GitOverHttpServlet$")) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static boolean is(String name, Class<?> type) {
 | 
			
		||||
    Class<?> p = type;
 | 
			
		||||
    while (p != null) {
 | 
			
		||||
      if (name.equals(p.getName())) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      p = p.getSuperclass();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Class<?>[] interfaces = type.getInterfaces();
 | 
			
		||||
    if (interfaces != null) {
 | 
			
		||||
      for (Class<?> i : interfaces) {
 | 
			
		||||
        if (is(name, i)) {
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -245,6 +245,7 @@ public class PluginLoader implements LifecycleListener {
 | 
			
		||||
    Attributes main = manifest.getMainAttributes();
 | 
			
		||||
    String sysName = main.getValue("Gerrit-Module");
 | 
			
		||||
    String sshName = main.getValue("Gerrit-SshModule");
 | 
			
		||||
    String httpName = main.getValue("Gerrit-HttpModule");
 | 
			
		||||
 | 
			
		||||
    URL[] urls = {jarFile.toURI().toURL()};
 | 
			
		||||
    ClassLoader parentLoader = PluginLoader.class.getClassLoader();
 | 
			
		||||
@@ -252,7 +253,9 @@ public class PluginLoader implements LifecycleListener {
 | 
			
		||||
 | 
			
		||||
    Class<? extends Module> sysModule = load(sysName, pluginLoader);
 | 
			
		||||
    Class<? extends Module> sshModule = load(sshName, pluginLoader);
 | 
			
		||||
    return new Plugin(name, jarFile, manifest, snapshot, sysModule, sshModule);
 | 
			
		||||
    Class<? extends Module> httpModule = load(httpName, pluginLoader);
 | 
			
		||||
    return new Plugin(name, jarFile, manifest, snapshot,
 | 
			
		||||
        sysModule, sshModule, httpModule);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Class<? extends Module> load(String name, ClassLoader pluginLoader)
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import static com.google.inject.Stage.PRODUCTION;
 | 
			
		||||
import com.google.gerrit.common.ChangeHookRunner;
 | 
			
		||||
import com.google.gerrit.ehcache.EhcachePoolImpl;
 | 
			
		||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
 | 
			
		||||
import com.google.gerrit.httpd.plugins.HttpPluginModule;
 | 
			
		||||
import com.google.gerrit.lifecycle.LifecycleManager;
 | 
			
		||||
import com.google.gerrit.lifecycle.LifecycleModule;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AuthType;
 | 
			
		||||
@@ -117,6 +118,7 @@ public class WebAppInitializer extends GuiceServletContextListener {
 | 
			
		||||
      PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
 | 
			
		||||
      env.setCfgInjector(cfgInjector);
 | 
			
		||||
      env.setSshInjector(sshInjector);
 | 
			
		||||
      env.setHttpInjector(webInjector);
 | 
			
		||||
 | 
			
		||||
      // Push the Provider<HttpServletRequest> down into the canonical
 | 
			
		||||
      // URL provider. Its optional for that provider, but since we can
 | 
			
		||||
@@ -228,6 +230,7 @@ public class WebAppInitializer extends GuiceServletContextListener {
 | 
			
		||||
    modules.add(sshInjector.getInstance(WebSshGlueModule.class));
 | 
			
		||||
    modules.add(CacheBasedWebSession.module());
 | 
			
		||||
    modules.add(HttpContactStoreConnection.module());
 | 
			
		||||
    modules.add(new HttpPluginModule());
 | 
			
		||||
 | 
			
		||||
    AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
 | 
			
		||||
    if (authConfig.getAuthType() == AuthType.OPENID) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user