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.WebModule;
|
||||||
import com.google.gerrit.httpd.WebSshGlueModule;
|
import com.google.gerrit.httpd.WebSshGlueModule;
|
||||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
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.LifecycleManager;
|
||||||
import com.google.gerrit.pgm.http.jetty.GetUserFilter;
|
import com.google.gerrit.pgm.http.jetty.GetUserFilter;
|
||||||
import com.google.gerrit.pgm.http.jetty.JettyEnv;
|
import com.google.gerrit.pgm.http.jetty.JettyEnv;
|
||||||
@@ -259,6 +260,9 @@ public class Daemon extends SiteProgram {
|
|||||||
private void initHttpd() {
|
private void initHttpd() {
|
||||||
webInjector = createWebInjector();
|
webInjector = createWebInjector();
|
||||||
|
|
||||||
|
sysInjector.getInstance(PluginGuiceEnvironment.class)
|
||||||
|
.setHttpInjector(webInjector);
|
||||||
|
|
||||||
sysInjector.getInstance(HttpCanonicalWebUrlProvider.class)
|
sysInjector.getInstance(HttpCanonicalWebUrlProvider.class)
|
||||||
.setHttpServletRequest(
|
.setHttpServletRequest(
|
||||||
webInjector.getProvider(HttpServletRequest.class));
|
webInjector.getProvider(HttpServletRequest.class));
|
||||||
@@ -273,6 +277,7 @@ public class Daemon extends SiteProgram {
|
|||||||
modules.add(HttpContactStoreConnection.module());
|
modules.add(HttpContactStoreConnection.module());
|
||||||
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
|
modules.add(sysInjector.getInstance(GitOverHttpModule.class));
|
||||||
modules.add(sysInjector.getInstance(WebModule.class));
|
modules.add(sysInjector.getInstance(WebModule.class));
|
||||||
|
modules.add(new HttpPluginModule());
|
||||||
if (sshd) {
|
if (sshd) {
|
||||||
modules.add(sshInjector.getInstance(WebSshGlueModule.class));
|
modules.add(sshInjector.getInstance(WebSshGlueModule.class));
|
||||||
modules.add(new ProjectQoSFilter.Module());
|
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.nio.SelectChannelConnector;
|
||||||
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
|
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
|
||||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
import org.eclipse.jetty.servlet.FilterMapping;
|
import org.eclipse.jetty.servlet.FilterMapping;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
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
|
// of using the listener to create the injector pass the one we
|
||||||
// already have built.
|
// 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() {
|
app.addEventListener(new GuiceServletContextListener() {
|
||||||
@Override
|
@Override
|
||||||
protected Injector getInjector() {
|
protected Injector getInjector() {
|
||||||
|
@@ -38,6 +38,17 @@ limitations under the License.
|
|||||||
<artifactId>gerrit-sshd</artifactId>
|
<artifactId>gerrit-sshd</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -20,6 +20,7 @@ import com.google.inject.AbstractModule;
|
|||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.servlet.GuiceFilter;
|
||||||
|
|
||||||
import org.eclipse.jgit.storage.file.FileSnapshot;
|
import org.eclipse.jgit.storage.file.FileSnapshot;
|
||||||
|
|
||||||
@@ -30,15 +31,24 @@ import java.util.jar.Manifest;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class Plugin {
|
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 String name;
|
||||||
private final File jar;
|
private final File jar;
|
||||||
private final Manifest manifest;
|
private final Manifest manifest;
|
||||||
private final FileSnapshot snapshot;
|
private final FileSnapshot snapshot;
|
||||||
private Class<? extends Module> sysModule;
|
private Class<? extends Module> sysModule;
|
||||||
private Class<? extends Module> sshModule;
|
private Class<? extends Module> sshModule;
|
||||||
|
private Class<? extends Module> httpModule;
|
||||||
|
|
||||||
private Injector sysInjector;
|
private Injector sysInjector;
|
||||||
private Injector sshInjector;
|
private Injector sshInjector;
|
||||||
|
private Injector httpInjector;
|
||||||
private LifecycleManager manager;
|
private LifecycleManager manager;
|
||||||
|
|
||||||
public Plugin(String name,
|
public Plugin(String name,
|
||||||
@@ -46,13 +56,15 @@ public class Plugin {
|
|||||||
Manifest manifest,
|
Manifest manifest,
|
||||||
FileSnapshot snapshot,
|
FileSnapshot snapshot,
|
||||||
@Nullable Class<? extends Module> sysModule,
|
@Nullable Class<? extends Module> sysModule,
|
||||||
@Nullable Class<? extends Module> sshModule) {
|
@Nullable Class<? extends Module> sshModule,
|
||||||
|
@Nullable Class<? extends Module> httpModule) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.jar = jar;
|
this.jar = jar;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.snapshot = snapshot;
|
this.snapshot = snapshot;
|
||||||
this.sysModule = sysModule;
|
this.sysModule = sysModule;
|
||||||
this.sshModule = sshModule;
|
this.sshModule = sshModule;
|
||||||
|
this.httpModule = httpModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
File getJar() {
|
File getJar() {
|
||||||
@@ -90,6 +102,13 @@ public class Plugin {
|
|||||||
manager.add(sshInjector);
|
manager.add(sshInjector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpModule != null && env.hasHttpModule()) {
|
||||||
|
httpInjector = sysInjector.createChildInjector(
|
||||||
|
env.getHttpModule(),
|
||||||
|
sysInjector.getInstance(httpModule));
|
||||||
|
manager.add(httpInjector);
|
||||||
|
}
|
||||||
|
|
||||||
manager.start();
|
manager.start();
|
||||||
env.onStartPlugin(this);
|
env.onStartPlugin(this);
|
||||||
}
|
}
|
||||||
@@ -113,6 +132,7 @@ public class Plugin {
|
|||||||
manager = null;
|
manager = null;
|
||||||
sysInjector = null;
|
sysInjector = null;
|
||||||
sshInjector = null;
|
sshInjector = null;
|
||||||
|
httpInjector = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +141,11 @@ public class Plugin {
|
|||||||
return sshInjector;
|
return sshInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Injector getHttpInjector() {
|
||||||
|
return httpInjector;
|
||||||
|
}
|
||||||
|
|
||||||
public void add(final RegistrationHandle handle) {
|
public void add(final RegistrationHandle handle) {
|
||||||
add(new LifecycleListener() {
|
add(new LifecycleListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@@ -45,6 +45,7 @@ public class PluginGuiceEnvironment {
|
|||||||
private final List<StartPluginListener> listeners;
|
private final List<StartPluginListener> listeners;
|
||||||
private Module sysModule;
|
private Module sysModule;
|
||||||
private Module sshModule;
|
private Module sshModule;
|
||||||
|
private Module httpModule;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
|
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
|
||||||
@@ -84,6 +85,19 @@ public class PluginGuiceEnvironment {
|
|||||||
return sshModule;
|
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) {
|
void onStartPlugin(Plugin plugin) {
|
||||||
for (StartPluginListener l : listeners) {
|
for (StartPluginListener l : listeners) {
|
||||||
l.onStartPlugin(plugin);
|
l.onStartPlugin(plugin);
|
||||||
@@ -126,15 +140,74 @@ public class PluginGuiceEnvironment {
|
|||||||
|
|
||||||
private static boolean shouldCopy(Key<?> key) {
|
private static boolean shouldCopy(Key<?> key) {
|
||||||
Class<?> type = key.getTypeLiteral().getRawType();
|
Class<?> type = key.getTypeLiteral().getRawType();
|
||||||
if (type == LifecycleListener.class) {
|
if (LifecycleListener.class.isAssignableFrom(type)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (type == StartPluginListener.class) {
|
if (StartPluginListener.class.isAssignableFrom(type)) {
|
||||||
return false;
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
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();
|
Attributes main = manifest.getMainAttributes();
|
||||||
String sysName = main.getValue("Gerrit-Module");
|
String sysName = main.getValue("Gerrit-Module");
|
||||||
String sshName = main.getValue("Gerrit-SshModule");
|
String sshName = main.getValue("Gerrit-SshModule");
|
||||||
|
String httpName = main.getValue("Gerrit-HttpModule");
|
||||||
|
|
||||||
URL[] urls = {jarFile.toURI().toURL()};
|
URL[] urls = {jarFile.toURI().toURL()};
|
||||||
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
|
ClassLoader parentLoader = PluginLoader.class.getClassLoader();
|
||||||
@@ -252,7 +253,9 @@ public class PluginLoader implements LifecycleListener {
|
|||||||
|
|
||||||
Class<? extends Module> sysModule = load(sysName, pluginLoader);
|
Class<? extends Module> sysModule = load(sysName, pluginLoader);
|
||||||
Class<? extends Module> sshModule = load(sshName, 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)
|
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.common.ChangeHookRunner;
|
||||||
import com.google.gerrit.ehcache.EhcachePoolImpl;
|
import com.google.gerrit.ehcache.EhcachePoolImpl;
|
||||||
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
|
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.LifecycleManager;
|
||||||
import com.google.gerrit.lifecycle.LifecycleModule;
|
import com.google.gerrit.lifecycle.LifecycleModule;
|
||||||
import com.google.gerrit.reviewdb.client.AuthType;
|
import com.google.gerrit.reviewdb.client.AuthType;
|
||||||
@@ -117,6 +118,7 @@ public class WebAppInitializer extends GuiceServletContextListener {
|
|||||||
PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
|
PluginGuiceEnvironment env = sysInjector.getInstance(PluginGuiceEnvironment.class);
|
||||||
env.setCfgInjector(cfgInjector);
|
env.setCfgInjector(cfgInjector);
|
||||||
env.setSshInjector(sshInjector);
|
env.setSshInjector(sshInjector);
|
||||||
|
env.setHttpInjector(webInjector);
|
||||||
|
|
||||||
// Push the Provider<HttpServletRequest> down into the canonical
|
// Push the Provider<HttpServletRequest> down into the canonical
|
||||||
// URL provider. Its optional for that provider, but since we can
|
// 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(sshInjector.getInstance(WebSshGlueModule.class));
|
||||||
modules.add(CacheBasedWebSession.module());
|
modules.add(CacheBasedWebSession.module());
|
||||||
modules.add(HttpContactStoreConnection.module());
|
modules.add(HttpContactStoreConnection.module());
|
||||||
|
modules.add(new HttpPluginModule());
|
||||||
|
|
||||||
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
|
AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
|
||||||
if (authConfig.getAuthType() == AuthType.OPENID) {
|
if (authConfig.getAuthType() == AuthType.OPENID) {
|
||||||
|
Reference in New Issue
Block a user