/*
* 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.bootstrapper;
import static org.jboss.errai.codegen.util.Stmt.loadVariable;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.jboss.errai.codegen.Context;
import org.jboss.errai.codegen.builder.BlockBuilder;
import org.jboss.errai.codegen.builder.ClassStructureBuilder;
import org.jboss.errai.codegen.builder.impl.BlockBuilderImpl;
import org.jboss.errai.codegen.meta.MetaClass;
import org.jboss.errai.codegen.meta.impl.build.BuildMetaClass;
import org.jboss.errai.codegen.util.Implementations;
import org.jboss.errai.codegen.util.Stmt;
import org.jboss.errai.common.client.api.annotations.IOCProducer;
import org.jboss.errai.common.metadata.MetaDataScanner;
import org.jboss.errai.common.metadata.ScannerSingleton;
import org.jboss.errai.common.server.api.ErraiBootstrapFailure;
import org.jboss.errai.config.rebind.EnvUtil;
import org.jboss.errai.config.util.ClassScanner;
import org.jboss.errai.ioc.client.Bootstrapper;
import org.jboss.errai.ioc.client.api.CodeDecorator;
import org.jboss.errai.ioc.client.api.EntryPoint;
import org.jboss.errai.ioc.client.api.IOCBootstrapTask;
import org.jboss.errai.ioc.client.api.IOCProvider;
import org.jboss.errai.ioc.client.api.TaskOrder;
import org.jboss.errai.ioc.client.api.SharedSingleton;
import org.jboss.errai.ioc.client.container.ContextManager;
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.injector.api.InjectionContext;
import org.jboss.errai.ioc.rebind.ioc.injector.api.WiringElementType;
import org.jboss.errai.ioc.util.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Multimap;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
/**
* Generator for the Bootstrapper class generated to wire an application at runtime.
*
* @author Mike Brock <cbrock@redhat.com>
*/
public class IOCBootstrapGenerator {
private final GeneratorContext context;
private final Set<String> packages;
private final boolean useReflectionStubs;
private final List<MetaClass> beforeTasks = new ArrayList<MetaClass>();
private final List<MetaClass> afterTasks = new ArrayList<MetaClass>();
public static final String QUALIFYING_METADATA_FACTORY_PROPERTY = "errai.ioc.QualifyingMetaDataFactory";
public static final String ENABLED_ALTERNATIVES_PROPERTY = "errai.ioc.enabled.alternatives";
public static final String WHITELIST_PROPERTY = "errai.ioc.whitelist";
public static final String BLACKLIST_PROPERTY = "errai.ioc.blacklist";
public static final String EXPERIMENTAL_INFER_DEPENDENT_BY_REACHABILITY
= "errai.ioc.experimental.infer_dependent_by_reachability";
private final TreeLogger logger;
private static final Logger log = LoggerFactory.getLogger(IOCBootstrapGenerator.class);
private static final Object generatorLock = new Object();
private static Set<Class<?>> iocExtensions;
private static List<IOCExtensionConfigurator> extensionConfigurators;
private static Collection<MetaClass> bootstrapClassCollection;
@SuppressWarnings("rawtypes")
private static Map<Class<? extends IOCDecoratorExtension>, Class<? extends Annotation>> decoratorMap;
public IOCBootstrapGenerator(final GeneratorContext context,
final TreeLogger logger,
final Set<String> packages,
final boolean useReflectionStubs) {
this.context = context;
this.logger = logger;
this.packages = packages;
this.useReflectionStubs = useReflectionStubs;
}
public String generate(final String packageName, final String className) {
synchronized (generatorLock) {
EnvUtil.recordEnvironmentState();
final String gen;
log.info("generating IOC bootstrapping class...");
final long st = System.currentTimeMillis();
log.debug("setting up injection context...");
final long injectionStart = System.currentTimeMillis();
final InjectionContext injectionContext = setupContexts(packageName, className);
log.debug("injection context setup in " + (System.currentTimeMillis() - injectionStart) + "ms");
gen = generateBootstrappingClassSource(injectionContext);
log.info("generated IOC bootstrapping class in " + (System.currentTimeMillis() - st) + "ms ");
return gen;
}
}
private InjectionContext setupContexts(final String packageName,
final String className) {
final boolean asyncBootstrap;
final String s = EnvUtil.getEnvironmentConfig().getFrameworkOrSystemProperty("errai.ioc.async_bean_manager");
asyncBootstrap = s != null && Boolean.parseBoolean(s);
final ClassStructureBuilder<?> classStructureBuilder =
Implementations.implement(Bootstrapper.class, packageName, className);
logger.log(com.google.gwt.core.ext.TreeLogger.Type.DEBUG, "Generating IOC Bootstrapper "
+ packageName + "." + className);
final BuildMetaClass bootStrapClass = classStructureBuilder.getClassDefinition();
final Context buildContext = bootStrapClass.getContext();
buildContext.addInterningCallback(new BootstrapInterningCallback(classStructureBuilder, buildContext));
final BlockBuilder<?> blockBuilder =
classStructureBuilder.publicMethod(ContextManager.class, "bootstrapContainer")
.methodComment("The main IOC bootstrap method.");
final IOCProcessingContext.Builder iocProcContextBuilder
= IOCProcessingContext.Builder.create();
iocProcContextBuilder.blockBuilder(blockBuilder);
iocProcContextBuilder.generatorContext(context);
iocProcContextBuilder.context(buildContext);
iocProcContextBuilder.bootstrapClassInstance(bootStrapClass);
iocProcContextBuilder.bootstrapBuilder(classStructureBuilder);
iocProcContextBuilder.logger(logger);
iocProcContextBuilder.gwtTarget(!useReflectionStubs);
final InjectionContext.Builder injectionContextBuilder
= InjectionContext.Builder.create();
final MetaDataScanner scanner = ScannerSingleton.getOrCreateInstance();
final Multimap<String, String> props = scanner.getErraiProperties();
if (props != null) {
logger.log(TreeLogger.Type.INFO, "Checking ErraiApp.properties for configured types ...");
final Collection<String> qualifyingMetadataFactoryProperties = props.get(QUALIFYING_METADATA_FACTORY_PROPERTY);
if (qualifyingMetadataFactoryProperties.size() > 1) {
throw new RuntimeException("the property '" + QUALIFYING_METADATA_FACTORY_PROPERTY + "' is set in more than one place");
}
final Collection<String> alternatives = PropertiesUtil.getPropertyValues(ENABLED_ALTERNATIVES_PROPERTY, "\\s");
for (final String alternative : alternatives) {
injectionContextBuilder.enabledAlternative(alternative.trim());
}
final Collection<String> whitelistItems = PropertiesUtil.getPropertyValues(WHITELIST_PROPERTY, "\\s");
for (final String item : whitelistItems) {
injectionContextBuilder.addToWhitelist(item.trim());
}
final Collection<String> blacklistItems = PropertiesUtil.getPropertyValues(BLACKLIST_PROPERTY, "\\s");
for (final String type : blacklistItems) {
injectionContextBuilder.addToBlacklist(type.trim());
}
}
iocProcContextBuilder.packages(packages);
final IOCProcessingContext processingContext = iocProcContextBuilder.build();
injectionContextBuilder.processingContext(processingContext);
injectionContextBuilder.asyncBootstrap(asyncBootstrap);
final InjectionContext injectionContext = injectionContextBuilder.build();
defaultConfigureProcessor(injectionContext);
return injectionContext;
}
private String generateBootstrappingClassSource(final InjectionContext injectionContext) {
log.debug("Processing IOC extensions...");
long start = System.currentTimeMillis();
processExtensions(context, injectionContext, beforeTasks, afterTasks);
log.debug("Extensions processed in {}ms", (System.currentTimeMillis() - start));
final IOCProcessor processorFactory = new IOCProcessor(injectionContext);
final IOCProcessingContext processingContext = injectionContext.getProcessingContext();
final ClassStructureBuilder<?> classBuilder = processingContext.getBootstrapBuilder();
final BlockBuilder<?> blockBuilder = processingContext.getBlockBuilder();
@SuppressWarnings({ "unchecked", "rawtypes" })
final BlockBuilder builder = new BlockBuilderImpl(classBuilder.getClassDefinition().getInstanceInitializer(), null);
doBeforeRunnables(builder);
log.debug("Process dependency graph...");
start = System.currentTimeMillis();
processorFactory.process(processingContext);
log.debug("Processed dependency graph in {}ms", System.currentTimeMillis() - start);
doAfterRunnbales(blockBuilder);
blockBuilder.append(loadVariable("contextManager").returnValue());
blockBuilder.finish();
start = System.currentTimeMillis();
final String bootstrapperImplString = classBuilder.toJavaString();
log.debug("Generated BootstrapperImpl String in {}ms", System.currentTimeMillis() - start);
return bootstrapperImplString;
}
private void doAfterRunnbales(final BlockBuilder<?> blockBuilder) {
long start;
log.debug("Running after tasks...");
start = System.currentTimeMillis();
_doRunnableTasks(afterTasks, blockBuilder);
log.debug("Tasks run in " + (System.currentTimeMillis() - start) + "ms");
}
private void doBeforeRunnables(final BlockBuilder<?> builder) {
long start;
log.debug("Running before tasks...");
start = System.currentTimeMillis();
_doRunnableTasks(beforeTasks, builder);
log.debug("Tasks run in " + (System.currentTimeMillis() - start) + "ms");
}
private static void _doRunnableTasks(final Collection<MetaClass> classes, final BlockBuilder<?> blockBuilder) {
for (final MetaClass clazz : classes) {
if (!clazz.isAssignableTo(Runnable.class)) {
throw new RuntimeException("annotated @IOCBootstrap task: " + clazz.getName() + " is not of type: "
+ Runnable.class.getName());
}
blockBuilder.append(Stmt.nestedCall(Stmt.newObject(clazz)).invoke("run"));
}
}
public static void processExtensions(final GeneratorContext context,
final InjectionContext injectionContext,
final List<MetaClass> beforeTasks,
final List<MetaClass> afterTasks) {
final MetaDataScanner scanner = ScannerSingleton.getOrCreateInstance();
maybeLoadExtensionConfigurators(scanner);
try {
for (final IOCExtensionConfigurator configurator : extensionConfigurators) {
configurator.configure(injectionContext.getProcessingContext(), injectionContext);
}
} catch (Exception e) {
throw new ErraiBootstrapFailure("Unable to run IOC Extension Configurator: " + e.getMessage(), e);
}
maybeLoadBootstrapClassCollection(context);
for (final MetaClass clazz : bootstrapClassCollection) {
final IOCBootstrapTask task = clazz.getAnnotation(IOCBootstrapTask.class);
if (task.value() == TaskOrder.Before) {
beforeTasks.add(clazz);
}
else {
afterTasks.add(clazz);
}
}
maybeValidateDecorators(scanner);
try {
for (@SuppressWarnings("rawtypes")
final Entry<Class<? extends IOCDecoratorExtension>, Class<? extends Annotation>> entry : decoratorMap.entrySet()) {
injectionContext.registerDecorator(
entry.getKey().getConstructor(new Class[] { Class.class }).newInstance(entry.getValue()));
}
} catch (Exception e) {
throw new ErraiBootstrapFailure("unable to load code decorator: " + e.getMessage(), e);
}
for (final IOCExtensionConfigurator extensionConfigurator : extensionConfigurators) {
extensionConfigurator.afterInitialization(injectionContext.getProcessingContext(), injectionContext);
}
}
@SuppressWarnings("rawtypes")
private static void maybeValidateDecorators(final MetaDataScanner scanner) {
if (decoratorMap == null || EnvUtil.isJUnitTest()) {
decoratorMap = new HashMap<Class<? extends IOCDecoratorExtension>, Class<? extends Annotation>>();
final Set<Class<?>> decorators = scanner.getTypesAnnotatedWith(CodeDecorator.class);
try {
for (final Class<?> clazz : decorators) {
final Class<? extends IOCDecoratorExtension> decoratorClass = clazz.asSubclass(IOCDecoratorExtension.class);
Class<? extends Annotation> annoType = null;
final Type t = decoratorClass.getGenericSuperclass();
if (!(t instanceof ParameterizedType)) {
throw new ErraiBootstrapFailure("code decorator must extend IOCDecoratorExtension<@AnnotationType>");
}
final ParameterizedType pType = (ParameterizedType) t;
if (IOCDecoratorExtension.class.equals(pType.getRawType())) {
if (pType.getActualTypeArguments().length == 0
|| !Annotation.class.isAssignableFrom((Class<?>) pType.getActualTypeArguments()[0])) {
throw new ErraiBootstrapFailure("code decorator must extend IOCDecoratorExtension<@AnnotationType>");
}
// noinspection unchecked
annoType = ((Class<?>) pType.getActualTypeArguments()[0]).asSubclass(Annotation.class);
}
decoratorMap.put(decoratorClass, annoType);
}
}
catch (Exception e) {
throw new ErraiBootstrapFailure("unable to load code decorator: " + e.getMessage(), e);
}
}
}
private static void maybeLoadBootstrapClassCollection(final GeneratorContext context) {
if (bootstrapClassCollection == null || EnvUtil.isJUnitTest()) {
bootstrapClassCollection = ClassScanner.getTypesAnnotatedWith(IOCBootstrapTask.class, context);
}
}
private static void maybeLoadExtensionConfigurators(final MetaDataScanner scanner) {
if (iocExtensions == null || extensionConfigurators == null || EnvUtil.isJUnitTest()) {
iocExtensions = scanner
.getTypesAnnotatedWith(org.jboss.errai.ioc.client.api.IOCExtension.class);
extensionConfigurators = new ArrayList<IOCExtensionConfigurator>();
try {
for (final Class<?> clazz : iocExtensions) {
final Class<? extends IOCExtensionConfigurator> configuratorClass
= clazz.asSubclass(IOCExtensionConfigurator.class);
final IOCExtensionConfigurator configurator = configuratorClass.newInstance();
extensionConfigurators.add(configurator);
}
}
catch (Exception e) {
throw new ErraiBootstrapFailure("unable to load IOC Extension Configurator: " + e.getMessage(), e);
}
}
}
/**
* @param injectionContext
* an instance of the injection context
*/
private static void defaultConfigureProcessor(final InjectionContext injectionContext) {
injectionContext.mapElementType(WiringElementType.PseudoScopedBean, Singleton.class);
injectionContext.mapElementType(WiringElementType.NormalScopedBean, ApplicationScoped.class);
injectionContext.mapElementType(WiringElementType.NormalScopedBean, SharedSingleton.class);
injectionContext.mapElementType(WiringElementType.PseudoScopedBean, EntryPoint.class);
injectionContext.mapElementType(WiringElementType.ProducerElement, IOCProducer.class);
injectionContext.mapElementType(WiringElementType.DependentBean, Dependent.class);
injectionContext.mapElementType(WiringElementType.Provider, IOCProvider.class);
injectionContext.mapElementType(WiringElementType.InjectionPoint, Inject.class);
injectionContext.mapElementType(WiringElementType.InjectionPoint, com.google.inject.Inject.class);
injectionContext.mapElementType(WiringElementType.AlternativeBean, Alternative.class);
}
}