/**
* The MIT License
*
* Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.hudsonci.inject.internal;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.name.Names;
import org.hudsonci.inject.SmoothieContainer;
import org.hudsonci.inject.internal.extension.ExtensionLocator;
import org.hudsonci.inject.internal.extension.ExtensionModule;
import org.hudsonci.inject.internal.extension.SmoothieExtensionLocator;
import org.hudsonci.inject.internal.plugin.PluginClassLoader;
import org.hudsonci.inject.internal.plugin.SmoothiePluginStrategy;
import hudson.PluginStrategy;
import hudson.PluginWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.guice.bean.binders.WireModule;
import org.sonatype.guice.bean.locators.DefaultBeanLocator;
import org.sonatype.guice.bean.locators.MutableBeanLocator;
import org.sonatype.guice.bean.reflect.ClassSpace;
import org.sonatype.guice.bean.reflect.URLClassSpace;
import org.sonatype.inject.BeanEntry;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* {@link SmoothieContainer} implementation.
*
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @since 1.397
*/
public class SmoothieContainerImpl
implements SmoothieContainer
{
private static final Logger log = LoggerFactory.getLogger(SmoothieContainerImpl.class);
private final MutableBeanLocator locator = new DefaultBeanLocator();
private final Injector root;
private final Map<PluginWrapper,Injector> injectors = new HashMap<PluginWrapper,Injector>();
public SmoothieContainerImpl(final Module... modules) {
this.root = createInjector(new BootModule(modules));
}
private Injector createInjector(final Module... modules) {
assert modules != null;
Injector injector = Guice.createInjector(new WireModule(modules));
if (log.isTraceEnabled()) {
log.trace("Created injector: {} w/bindings:", OID.get(injector));
for (Map.Entry<Key<?>,Binding<?>> entry : injector.getAllBindings().entrySet()) {
log.trace(" {} -> {}", entry.getKey(), entry.getValue());
}
}
return injector;
}
/**
* Not officially part of {@link SmoothieContainer} API, exposed for {@link org.hudsonci.inject.injecto.Injectomatic}.
*
* @since 1.397
*/
public Injector rootInjector() {
return root;
}
/**
* Common bindings.
*/
private class CommonModule
extends AbstractModule
{
private final Module[] modules;
private CommonModule(final Module[] modules) {
assert modules != null;
this.modules = modules;
}
private CommonModule() {
this(new Module[0]);
}
@Override
protected void configure() {
bind(MutableBeanLocator.class).toInstance(locator);
bind(SmoothieContainer.class).toInstance(SmoothieContainerImpl.this);
install(new HudsonModule());
for (Module module : modules) {
install(module);
}
}
}
/**
* Bindings for bootstrapping container bits. Scan path needs to be configured as additional module.
*/
private class BootModule
extends CommonModule
{
private BootModule(final Module[] modules) {
super(modules);
}
@Override
protected void configure() {
bind(PluginStrategy.class).annotatedWith(Names.named("default")).to(SmoothiePluginStrategy.class);
bind(ExtensionLocator.class).annotatedWith(Names.named("default")).to(SmoothieExtensionLocator.class);
super.configure();
}
}
// FIXME: Bootstrap probably needs to be aware of WEB-INF/lib/* bits, and should include its own ExtensionModule?
/**
* Bindings and class space for plugins.
*/
private class PluginModule
extends CommonModule
{
private final PluginWrapper plugin;
private PluginModule(final PluginWrapper plugin) {
assert plugin != null;
this.plugin = plugin;
}
@Override
protected void configure() {
ClassSpace space = createClassSpace();
install(new ExtensionModule(space, false));
super.configure();
}
private ClassSpace createClassSpace() {
URLClassSpace space;
if (plugin.classLoader instanceof PluginClassLoader) {
PluginClassLoader cl = (PluginClassLoader) plugin.classLoader;
space = new URLClassSpace(cl, cl.getURLs()); // urls logged from PluginWrapperFactory
}
else {
log.warn("Expected plugin to have PluginClassLoader; instead found: {}", plugin.classLoader.getClass().getName());
space = new URLClassSpace(plugin.classLoader);
}
return space;
}
}
public void register(final PluginWrapper plugin) {
assert plugin != null;
if (log.isTraceEnabled()) {
log.trace("Registering plugin: {}", plugin.getShortName());
}
// Don't allow re-registration of plugins
if (injectors.containsKey(plugin)) {
throw new IllegalStateException("Plugin already registered");
}
Injector injector = createInjector(new PluginModule(plugin));
injectors.put(plugin, injector);
}
public Injector injector(final PluginWrapper plugin) {
assert plugin != null;
Injector injector = injectors.get(plugin);
// All plugins must be registered
if (injector == null) {
throw new IllegalStateException("Plugin not registered");
}
return injector;
}
public <Q extends Annotation, T> Iterable<BeanEntry<Q, T>> locate(final Key<T> key) {
return locator.locate(key);
}
public <T> T get(final Key<T> key) {
Iterator<BeanEntry<Annotation,T>> iter = locate(key).iterator();
assert iter != null;
if (iter.hasNext()) {
BeanEntry<Annotation,T> bean = iter.next();
log.debug("Found: {}", bean);
T value = bean.getValue();
// If more than one component is found, complain softly
if (log.isDebugEnabled()) {
if (iter.hasNext()) {
log.debug("More than one instance bound for key: {}", key);
while (iter.hasNext()) {
log.debug(" {}", iter.next());
}
}
}
return value;
}
throw new RuntimeException("No object bound for key: " + key);
}
}