blob: 4b6f49710c75c461359a596d360d6b6ed7f656c5 [file] [log] [blame]
// 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.server.plugins;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
/**
* Tracks Guice bindings that should be exposed to loaded plugins.
* <p>
* This is an internal implementation detail of how the main server is able to
* export its explicit Guice bindings to tightly coupled plugins, giving them
* access to singletons and request scoped resources just like any core code.
*/
@Singleton
public class PluginGuiceEnvironment {
private final Injector sysInjector;
private final CopyConfigModule copyConfigModule;
private final List<StartPluginListener> listeners;
private Module sysModule;
private Module sshModule;
private Module httpModule;
@Inject
PluginGuiceEnvironment(Injector sysInjector, CopyConfigModule ccm) {
this.sysInjector = sysInjector;
this.copyConfigModule = ccm;
this.listeners = new CopyOnWriteArrayList<StartPluginListener>();
this.listeners.addAll(getListeners(sysInjector));
}
Module getSysModule() {
return sysModule;
}
public void setCfgInjector(Injector cfgInjector) {
final Module cm = copy(cfgInjector);
final Module sm = copy(sysInjector);
sysModule = new AbstractModule() {
@Override
protected void configure() {
install(copyConfigModule);
install(cm);
install(sm);
}
};
}
public void setSshInjector(Injector sshInjector) {
sshModule = copy(sshInjector);
listeners.addAll(getListeners(sshInjector));
}
boolean hasSshModule() {
return sshModule != null;
}
Module getSshModule() {
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);
}
}
private static List<StartPluginListener> getListeners(Injector src) {
List<Binding<StartPluginListener>> bindings =
src.findBindingsByType(new TypeLiteral<StartPluginListener>() {});
List<StartPluginListener> found =
Lists.newArrayListWithCapacity(bindings.size());
for (Binding<StartPluginListener> b : bindings) {
found.add(b.getProvider().get());
}
return found;
}
private static Module copy(Injector src) {
final Map<Key<?>, Binding<?>> bindings = Maps.newLinkedHashMap();
for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
if (shouldCopy(e.getKey())) {
bindings.put(e.getKey(), e.getValue());
}
}
bindings.remove(Key.get(Injector.class));
bindings.remove(Key.get(java.util.logging.Logger.class));
return new AbstractModule() {
@SuppressWarnings("unchecked")
@Override
protected void configure() {
for (Map.Entry<Key<?>, Binding<?>> e : bindings.entrySet()) {
Key<Object> k = (Key<Object>) e.getKey();
Binding<Object> b = (Binding<Object>) e.getValue();
bind(k).toProvider(b.getProvider());
}
}
};
}
private static boolean shouldCopy(Key<?> key) {
Class<?> type = key.getTypeLiteral().getRawType();
if (LifecycleListener.class.isAssignableFrom(type)) {
return false;
}
if (StartPluginListener.class.isAssignableFrom(type)) {
return false;
}
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;
}
}