/*
* Copyright (C) 2011 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.ioc.rebind.ioc.injector.api;
import static java.util.Collections.unmodifiableCollection;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.NormalScope;
import javax.enterprise.inject.Stereotype;
import javax.inject.Qualifier;
import javax.inject.Scope;
import org.jboss.errai.codegen.meta.HasAnnotations;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.config.util.ClassScanner;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.FactoryBodyGenerator;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.FactoryGenerator;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.IOCProcessingContext;
import org.jboss.errai.ioc.rebind.ioc.bootstrapper.IOCProcessor;
import org.jboss.errai.ioc.rebind.ioc.extension.IOCDecoratorExtension;
import org.jboss.errai.ioc.rebind.ioc.extension.IOCExtensionConfigurator;
import org.jboss.errai.ioc.rebind.ioc.graph.api.QualifierFactory;
import org.jboss.errai.ioc.rebind.ioc.graph.impl.DefaultQualifierFactory;
import org.jboss.errai.ioc.rebind.ioc.graph.impl.FactoryNameGenerator;
import org.jboss.errai.ioc.rebind.ioc.graph.impl.InjectableHandle;
import org.jboss.errai.reflections.util.SimplePackageFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
/**
* At every rebind phase, a single {@link InjectionContext} is used. It contains
* information on enabled alternatives, whitelisted and blacklisted beans, and
* annotations associated with various {@link WiringElementType wiring types}.
*
* The injection context also stores string-named attributes for sharing data
* between the {@link IOCProcessor} and separate usages of the
* {@link FactoryGenerator}.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class InjectionContext {
private static final Logger log = LoggerFactory.getLogger(InjectionContext.class);
private final IOCProcessingContext processingContext;
private final Multimap<WiringElementType, Class<? extends Annotation>> elementBindings = HashMultimap.create();
private final Multimap<InjectableHandle, InjectableProvider> injectableProviders = HashMultimap.create();
private final Multimap<InjectableHandle, InjectableProvider> exactTypeInjectableProviders = HashMultimap.create();
private final Collection<ExtensionTypeCallback> extensionTypeCallbacks = new ArrayList<ExtensionTypeCallback>();
private final boolean async;
private final QualifierFactory qualifierFactory;
private final Set<String> whitelist;
private final Set<String> blacklist;
private static final String[] implicitWhitelist = { "org.jboss.errai.*", "com.google.gwt.*" };
private final Multimap<Class<? extends Annotation>, IOCDecoratorExtension<? extends Annotation>> decorators = HashMultimap.create();
private final Multimap<ElementType, Class<? extends Annotation>> decoratorsByElementType = HashMultimap.create();
private final Multimap<Class<? extends Annotation>, Class<? extends Annotation>> metaAnnotationAliases
= HashMultimap.create();
private final Map<String, Object> attributeMap = new HashMap<String, Object>();
private InjectionContext(final Builder builder) {
this.processingContext = Assert.notNull(builder.processingContext);
if (builder.qualifierFactory == null) {
this.qualifierFactory = new DefaultQualifierFactory();
} else {
this.qualifierFactory = builder.qualifierFactory;
}
this.whitelist = Assert.notNull(builder.whitelist);
this.blacklist = Assert.notNull(builder.blacklist);
this.async = builder.async;
}
public static class Builder {
private IOCProcessingContext processingContext;
private boolean async;
private QualifierFactory qualifierFactory;
private final HashSet<String> enabledAlternatives = new HashSet<String>();
private final HashSet<String> whitelist = new HashSet<String>();
private final HashSet<String> blacklist = new HashSet<String>();
public static Builder create() {
return new Builder();
}
public Builder qualifierFactory(final QualifierFactory qualifierFactory) {
this.qualifierFactory = qualifierFactory;
return this;
}
public Builder processingContext(final IOCProcessingContext processingContext) {
this.processingContext = processingContext;
return this;
}
public Builder enabledAlternative(final String fqcn) {
enabledAlternatives.add(fqcn);
return this;
}
public Builder addToWhitelist(final String item) {
whitelist.add(item);
return this;
}
public Builder addToBlacklist(final String item) {
blacklist.add(item);
return this;
}
public Builder asyncBootstrap(final boolean async) {
this.async = async;
return this;
}
public InjectionContext build() {
Assert.notNull("the processingContext cannot be null", processingContext);
return new InjectionContext(this);
}
}
/**
* Register an {@link InjectableProvider} for injection sites that are
* sastisfied by the given {@link InjectableHandle}.
*
* @param handle
* Contains the type and qualifier that the given provider satisfies.
* @param provider
* The
* {@link InjectableProvider#getInjectable(org.jboss.errai.ioc.rebind.ioc.graph.api.ProvidedInjectable.InjectionSite, FactoryNameGenerator)}
* will be called for every injection site satisified by the given
* handle. The returned {@link FactoryBodyGenerator} will be used to
* generate factories specific to the given injection sites.
*/
public void registerInjectableProvider(final InjectableHandle handle, final InjectableProvider provider) {
injectableProviders.put(handle, provider);
}
/**
* Like
* {@link #registerInjectableProvider(InjectableHandle, InjectableProvider)},
* but only injection sites with the exact type of the given
* {@link InjectableHandle} are satisfied.
*
* @param handle
* Contains the exact type and qualifier that the given provider
* satisfies.
* @param provider
* The
* {@link InjectableProvider#getInjectable(org.jboss.errai.ioc.rebind.ioc.graph.api.ProvidedInjectable.InjectionSite, FactoryNameGenerator)}
* will be called for every injection site satisified by the given
* handle. The returned {@link FactoryBodyGenerator} will be used to
* generate factories specific to the given injection sites.
*/
public void registerExactTypeInjectableProvider(final InjectableHandle handle, final InjectableProvider provider) {
exactTypeInjectableProviders.put(handle, provider);
}
/**
* An {@link ExtensionTypeCallback} registered with this method will be called for every type processed the Errai IoC
* before the dependency graph is built. This gives {@link IOCExtensionConfigurator IOCExtensionConfigurators} a way
* of registering {@link #registerInjectableProvider(InjectableHandle, InjectableProvider) injectable providers}
* dynamically.
*
* @param callback Never null.
*/
public void registerExtensionTypeCallback(final ExtensionTypeCallback callback) {
extensionTypeCallbacks.add(callback);
}
public Collection<ExtensionTypeCallback> getExtensionTypeCallbacks() {
return Collections.unmodifiableCollection(extensionTypeCallbacks);
}
public Multimap<InjectableHandle, InjectableProvider> getInjectableProviders() {
return Multimaps.unmodifiableMultimap(injectableProviders);
}
public Multimap<InjectableHandle, InjectableProvider> getExactTypeInjectableProviders() {
return Multimaps.unmodifiableMultimap(exactTypeInjectableProviders);
}
public QualifierFactory getQualifierFactory() {
return qualifierFactory;
}
public boolean isIncluded(final MetaClass type) {
return isWhitelisted(type) && !isBlacklisted(type);
}
public boolean isWhitelisted(final MetaClass type) {
if (whitelist.isEmpty()) {
return true;
}
final SimplePackageFilter implicitFilter = new SimplePackageFilter(Arrays.asList(implicitWhitelist));
final SimplePackageFilter whitelistFilter = new SimplePackageFilter(whitelist);
final String fullName = type.getFullyQualifiedName();
return implicitFilter.apply(fullName) || whitelistFilter.apply(fullName);
}
public boolean isBlacklisted(final MetaClass type) {
final SimplePackageFilter blacklistFilter = new SimplePackageFilter(blacklist);
final String fullName = type.getFullyQualifiedName();
return blacklistFilter.apply(fullName);
}
public void registerDecorator(final IOCDecoratorExtension<?> iocExtension) {
final Class<? extends Annotation> annotation = iocExtension.decoratesWith();
final Target target = annotation.getAnnotation(Target.class);
if (target != null) {
final boolean oneTarget = target.value().length == 1;
for (final ElementType type : target.value()) {
if (type == ElementType.ANNOTATION_TYPE) {
// type is a meta-annotation. so we need to map all annotations with this
// meta-annotation to the decorator extension.
for (final MetaClass annotationClazz : ClassScanner.getTypesAnnotatedWith(annotation,
processingContext.getGeneratorContext())) {
if (Annotation.class.isAssignableFrom(annotationClazz.asClass())) {
final Class<? extends Annotation> javaAnnoCls = annotationClazz.asClass().asSubclass(Annotation.class);
decorators.get(javaAnnoCls).add(iocExtension);
if (oneTarget) {
metaAnnotationAliases.put(javaAnnoCls, annotation);
}
}
}
if (oneTarget) {
return;
}
}
}
}
decorators.get(annotation).add(iocExtension);
}
public Set<Class<? extends Annotation>> getDecoratorAnnotations() {
return Collections.unmodifiableSet(decorators.keySet());
}
public <A extends Annotation> IOCDecoratorExtension<A>[] getDecorators(final Class<A> annotation) {
final Collection<IOCDecoratorExtension<?>> decs = decorators.get(annotation);
@SuppressWarnings("unchecked")
final IOCDecoratorExtension<A>[] da = new IOCDecoratorExtension[decs.size()];
decs.toArray(da);
return da;
}
public Collection<Class<? extends Annotation>> getDecoratorAnnotationsBy(final ElementType type) {
if (decoratorsByElementType.size() == 0) {
sortDecorators();
}
if (decoratorsByElementType.containsKey(type)) {
return unmodifiableCollection(decoratorsByElementType.get(type));
}
else {
return Collections.emptySet();
}
}
public boolean isMetaAnnotationFor(final Class<? extends Annotation> alias, final Class<? extends Annotation> forAnno) {
return metaAnnotationAliases.containsEntry(alias, forAnno);
}
private void sortDecorators() {
for (final Class<? extends Annotation> a : getDecoratorAnnotations()) {
if (a.isAnnotationPresent(Target.class)) {
for (final ElementType type : a.getAnnotation(Target.class).value()) {
decoratorsByElementType.get(type).add(a);
}
}
else {
for (final ElementType type : ElementType.values()) {
decoratorsByElementType.get(type).add(a);
}
}
}
}
public IOCProcessingContext getProcessingContext() {
return processingContext;
}
public void mapElementType(final WiringElementType type, final Class<? extends Annotation> annotationType) {
elementBindings.put(type, annotationType);
}
public Collection<Class<? extends Annotation>> getAnnotationsForElementType(final WiringElementType type) {
return unmodifiableCollection(elementBindings.get(type));
}
public boolean isAnyKnownElementType(final HasAnnotations hasAnnotations) {
return isAnyOfElementTypes(hasAnnotations, WiringElementType.values());
}
public boolean isAnyOfElementTypes(final HasAnnotations hasAnnotations, final WiringElementType... types) {
for (final WiringElementType t : types) {
if (isElementType(t, hasAnnotations))
return true;
}
return false;
}
public boolean isElementType(final WiringElementType type, final HasAnnotations hasAnnotations) {
final Annotation matchingAnnotation = getMatchingAnnotationForElementType(type, hasAnnotations);
if (matchingAnnotation != null && type == WiringElementType.NotSupported) {
log.error(hasAnnotations + " was annotated with " + matchingAnnotation.annotationType().getName()
+ " which is not supported in client-side Errai code!");
}
return matchingAnnotation != null;
}
public boolean isElementType(final WiringElementType type, final Class<? extends Annotation> annotation) {
return getAnnotationsForElementType(type).contains(annotation);
}
/**
* Overloaded version to check GWT's JClassType classes.
*
* @param type
* @param hasAnnotations
*
* @return
*/
public boolean isElementType(final WiringElementType type,
final com.google.gwt.core.ext.typeinfo.HasAnnotations hasAnnotations) {
final Collection<Class<? extends Annotation>> annotationsForElementType = getAnnotationsForElementType(type);
for (final Annotation a : hasAnnotations.getAnnotations()) {
if (annotationsForElementType.contains(a.annotationType())) {
return true;
}
}
return false;
}
public Annotation getMatchingAnnotationForElementType(final WiringElementType type,
final HasAnnotations hasAnnotations) {
final Collection<Class<? extends Annotation>> annotationsForElementType = getAnnotationsForElementType(type);
for (final Annotation a : hasAnnotations.getAnnotations()) {
if (annotationsForElementType.contains(a.annotationType())) {
return a;
}
}
final Set<Annotation> annotationSet = new HashSet<Annotation>();
fillInStereotypes(annotationSet, hasAnnotations.getAnnotations(), false);
for (final Annotation a : annotationSet) {
if (annotationsForElementType.contains(a.annotationType())) {
return a;
}
}
return null;
}
private static void fillInStereotypes(final Set<Annotation> annotationSet,
final Annotation[] from,
boolean filterScopes) {
final List<Class<? extends Annotation>> stereotypes
= new ArrayList<Class<? extends Annotation>>();
for (final Annotation a : from) {
final Class<? extends Annotation> aClass = a.annotationType();
if (aClass.isAnnotationPresent(Stereotype.class)) {
stereotypes.add(aClass);
}
else if (!filterScopes &&
aClass.isAnnotationPresent(NormalScope.class) ||
aClass.isAnnotationPresent(Scope.class)) {
filterScopes = true;
annotationSet.add(a);
}
else if (aClass.isAnnotationPresent(Qualifier.class)) {
annotationSet.add(a);
}
}
for (final Class<? extends Annotation> stereotype : stereotypes) {
fillInStereotypes(annotationSet, stereotype.getAnnotations(), filterScopes);
}
}
public Collection<Map.Entry<WiringElementType, Class<? extends Annotation>>> getAllElementMappings() {
return unmodifiableCollection(elementBindings.entries());
}
public void setAttribute(final String name, final Object value) {
attributeMap.put(name, value);
}
public Object getAttribute(final String name) {
return attributeMap.get(name);
}
public boolean hasAttribute(final String name) {
return attributeMap.containsKey(name);
}
public boolean isAsync() {
return async;
}
}