/**
* Copyright (C) 2012 Ness Computing, Inc.
*
* 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.nesscomputing.httpclient.guice;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.nesscomputing.config.ConfigProvider;
import com.nesscomputing.httpclient.HttpClient;
import com.nesscomputing.httpclient.HttpClientDefaults;
import com.nesscomputing.httpclient.HttpClientObserver;
import com.nesscomputing.httpclient.HttpClientObserverGroup;
import com.nesscomputing.httpclient.factory.httpclient4.ApacheHttpClient4Factory;
import com.nesscomputing.httpclient.internal.HttpClientFactory;
import com.nesscomputing.lifecycle.LifecycleStage;
import com.nesscomputing.lifecycle.guice.AbstractLifecycleProvider;
import com.nesscomputing.lifecycle.guice.LifecycleAction;
import com.nesscomputing.logging.Log;
/**
* Guice module to bind an instance of a HttpClient. Each HttpClient should be annotated or named so that
* other pieces of code can request a specific instance of the HttpClient.
*/
public class HttpClientModule extends AbstractModule
{
private static final Log LOG = Log.findLog();
private static final String OBSERVER_GROUP = "__observer_groups";
private static final String INHERIT_MAP = "__observer_group_inherit";
private final String clientName;
private final Set<HttpClientObserverGroup> observerGroups;
/**
* Bind an named HttpClient instance.
* @param clientName The name to use. This can be used with the Named annotation in other parts of the code.
*/
public HttpClientModule(@Nonnull final String clientName, @Nonnull final HttpClientObserverGroup... observerGroups)
{
Preconditions.checkArgument(clientName != null, "client name can not be null");
this.clientName = clientName;
this.observerGroups = ImmutableSet.copyOf(observerGroups);
}
@Override
public void configure()
{
final Map<String, String> optionMap = Collections.singletonMap("httpclient_name", clientName);
final Annotation annotation = Names.named(clientName);
bind(HttpClientDefaults.class).annotatedWith(annotation).toProvider(ConfigProvider.of(null, HttpClientDefaults.class, optionMap)).in(Scopes.SINGLETON);
bind(HttpClientFactory.class).annotatedWith(annotation).toProvider(new ApacheHttpClient4FactoryProvider(annotation, observerGroups)).in(Scopes.SINGLETON);
bind(HttpClient.class).annotatedWith(annotation).toProvider(new HttpClientProvider(annotation)).asEagerSingleton();
MapBinder.newMapBinder(binder(), HttpClientObserverGroup.class, HttpClientObserver.class, Names.named(OBSERVER_GROUP)).permitDuplicates();
MapBinder.newMapBinder(binder(), HttpClientObserverGroup.class, HttpClientObserverGroup.class, Names.named(INHERIT_MAP)).permitDuplicates();
}
/**
* Register a HttpClientObserver which observes *every* Guice-bound HttpClient.
* @return the binding builder you should register with
*/
public static LinkedBindingBuilder<HttpClientObserver> bindNewObserver(final Binder binder)
{
return Multibinder.newSetBinder(binder, HttpClientObserver.class).addBinding();
}
/**
* Register a HttpClientObserver which observes only requests from a HttpClient with the given Guice binding annotation.
* @return the binding builder you should register with
*/
public static LinkedBindingBuilder<HttpClientObserver> bindNewObserver(final Binder binder, final Annotation annotation)
{
return Multibinder.newSetBinder(binder, HttpClientObserver.class, annotation).addBinding();
}
/**
* Register a HttpClientObserver which observes requests from any HttpClient that is given the specified
* {@link HttpClientObserverGroup}.
*/
public static LinkedBindingBuilder<HttpClientObserver> bindNewObserver(final Binder binder, final HttpClientObserverGroup observerGroup)
{
return MapBinder.newMapBinder(binder, HttpClientObserverGroup.class, HttpClientObserver.class, Names.named(OBSERVER_GROUP)).permitDuplicates().addBinding(observerGroup);
}
public static void addObserverGroupInheritance(final Binder binder, final HttpClientObserverGroup subGroup, final HttpClientObserverGroup superGroup)
{
MapBinder.newMapBinder(binder, HttpClientObserverGroup.class, HttpClientObserverGroup.class, Names.named(INHERIT_MAP)).permitDuplicates().addBinding(subGroup).toInstance(superGroup);
}
/**
* Provides an instance of a HttpClient. Retrieves all its dependencies based off an annotation.
*/
static final class HttpClientProvider extends AbstractLifecycleProvider<HttpClient> implements Provider<HttpClient>
{
private HttpClientDefaults httpClientDefaults = null;
private HttpClientFactory httpClientFactory = null;
private final Annotation annotation;
private HttpClientProvider(@Nonnull final Annotation annotation)
{
this.annotation = annotation;
addAction(LifecycleStage.START_STAGE, new LifecycleAction<HttpClient>() {
@Override
public void performAction(final HttpClient httpClient) {
httpClient.start();
}
});
addAction(LifecycleStage.STOP_STAGE, new LifecycleAction<HttpClient>() {
@Override
public void performAction(final HttpClient httpClient) {
httpClient.stop();
}
});
}
@Inject
public void setInjector(final Injector injector)
{
this.httpClientDefaults = injector.getInstance(Key.get(HttpClientDefaults.class, annotation));
this.httpClientFactory = injector.getInstance(Key.get(HttpClientFactory.class, annotation));
}
@Override
public HttpClient internalGet()
{
return new HttpClient(httpClientFactory, httpClientDefaults);
}
}
static final class ApacheHttpClient4FactoryProvider implements Provider<HttpClientFactory>
{
private static final TypeLiteral<Set<HttpClientObserver>> OBSERVER_TYPE_LITERAL = new TypeLiteral<Set<HttpClientObserver>>() {};
private final Annotation annotation;
private final Set<HttpClientObserverGroup> observerGroups;
private Injector injector;
private Map<HttpClientObserverGroup, Set<HttpClientObserver>> groupObserverMap;
private Map<HttpClientObserverGroup, Set<HttpClientObserverGroup>> groupInheritanceMap;
private ApacheHttpClient4FactoryProvider(@Nonnull final Annotation annotation, final Set<HttpClientObserverGroup> observerGroups)
{
this.annotation = annotation;
this.observerGroups = observerGroups;
}
@Inject
void setInjector(final Injector injector, @Named(OBSERVER_GROUP) Map<HttpClientObserverGroup, Set<HttpClientObserver>> groupObserverMap, @Named(INHERIT_MAP) Map<HttpClientObserverGroup, Set<HttpClientObserverGroup>> groupInheritanceMap)
{
this.injector = injector;
this.groupObserverMap = groupObserverMap;
this.groupInheritanceMap = groupInheritanceMap;
}
private Set<HttpClientObserver> findObservers(final Key<Set<HttpClientObserver>> key)
{
if (injector.getExistingBinding(key) != null) {
return injector.getInstance(key);
}
else {
return Collections.emptySet();
}
}
private Set<HttpClientObserver> findObserversForGroups()
{
// Keep track of seen groups so that we cannot recurse infinitely if there is a cycle
final Set<HttpClientObserverGroup> seen = new HashSet<>();
final Set<HttpClientObserver> result = new HashSet<>();
for (HttpClientObserverGroup group : observerGroups) {
result.addAll(findObservers(group, seen));
}
return result;
}
private Set<HttpClientObserver> findObservers(HttpClientObserverGroup group, Set<HttpClientObserverGroup> seen)
{
if (!seen.add(group)) {
return Collections.emptySet();
}
HashSet<HttpClientObserver> result = new HashSet<>();
Set<HttpClientObserver> observers = groupObserverMap.get(group);
if (observers != null) {
result.addAll(observers);
}
Set<HttpClientObserverGroup> inheritGroups = groupInheritanceMap.get(group);
if (inheritGroups != null) {
for (HttpClientObserverGroup superGroup : inheritGroups) {
result.addAll(findObservers(superGroup, seen));
}
}
return result;
}
@Override
public HttpClientFactory get()
{
final Set<HttpClientObserver> httpClientObservers = ImmutableSet.<HttpClientObserver>builder()
.addAll(findObservers(Key.get(OBSERVER_TYPE_LITERAL)))
.addAll(findObservers(Key.get(OBSERVER_TYPE_LITERAL, annotation)))
.addAll(findObserversForGroups())
.build();
LOG.info("HttpClient '%s' has observers: %s", annotation, httpClientObservers);
final HttpClientDefaults httpClientDefaults = injector.getInstance(Key.get(HttpClientDefaults.class, annotation));
return new ApacheHttpClient4Factory(httpClientDefaults, httpClientObservers);
}
}
}