/* * 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; } }