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