/*
* 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.client.container;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Alternative;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.ioc.client.JsArray;
import org.jboss.errai.ioc.client.QualifierUtil;
import org.jboss.errai.ioc.client.WindowInjectionContext;
import org.jboss.errai.ioc.client.WindowInjectionContextStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
/**
* A simple bean manager provided by the Errai IOC framework. The manager provides access to all of the wired beans
* and their instances. Since the actual wiring code is generated, the bean manager is populated by the generated
* code at bootstrap time.
*
* @author Max Barkley <mbarkley@redhat.com>
* @author Mike Brock
* @author Christian Sadilek <csadilek@redhat.com>
*/
@Alternative
public class SyncBeanManagerImpl implements SyncBeanManager, BeanManagerSetup {
private static final Logger logger = LoggerFactory.getLogger(SyncBeanManager.class);
private ContextManager contextManager;
private final Multimap<String, FactoryHandle> handlesByName = ArrayListMultimap.create();
private final Multimap<String, SyncBeanDef<?>> runtimeBeanDefsByName = ArrayListMultimap.create();
@Override
public void destroyBean(final Object ref) {
contextManager.destroy(ref);
}
@Override
public boolean isManaged(final Object ref) {
return contextManager.isManaged(ref);
}
@Override
public Object getActualBeanReference(final Object ref) {
return Factory.maybeUnwrapProxy(ref);
}
@Override
public boolean isProxyReference(final Object ref) {
return ref instanceof Proxy;
}
@Override
public boolean addDestructionCallback(final Object beanInstance, final DestructionCallback<?> destructionCallback) {
return contextManager.addDestructionCallback(beanInstance, destructionCallback);
}
@Override
public void setContextManager(final ContextManager contextManager) {
if (this.contextManager != null) {
throw new RuntimeException("The ContextManager must only be set once.");
}
this.contextManager = contextManager;
init();
}
private void init() {
final Collection<FactoryHandle> eager = new ArrayList<>();
for (final FactoryHandle handle : contextManager.getAllFactoryHandles()) {
if (handle.isEager()) {
eager.add(handle);
}
addFactory(handle);
}
for (final FactoryHandle handle : eager) {
contextManager.getEagerInstance(handle.getFactoryName());
}
}
private void addFactory(final FactoryHandle handle) {
for (final Class<?> assignableType : handle.getAssignableTypes()) {
handlesByName.put(assignableType.getName(), handle);
}
if (handle.getBeanName() != null) {
handlesByName.put(handle.getBeanName(), handle);
}
}
@SuppressWarnings({ "rawtypes" })
@Override
public Collection<SyncBeanDef> lookupBeans(final String name) {
return lookupBeans(name, false);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Collection<SyncBeanDef> lookupBeans(final String name, final boolean keepJsDups) {
Assert.notNull(name);
logger.debug("Looking up beans for {}", name);
final Collection<FactoryHandle> handles = handlesByName.get(name);
final Collection<SyncBeanDef<?>> runtimeBeanDefs = runtimeBeanDefsByName.get(name);
final JsArray<JsTypeProvider<?>> jsProviders = getJsProviders(name);
final Set<String> beanDefFactoryNames = new HashSet<>();
final Collection beanDefs = new ArrayList<SyncBeanDef<Object>>(handles.size()+runtimeBeanDefs.size()+jsProviders.length());
beanDefs.addAll(runtimeBeanDefs);
for (final FactoryHandle handle : handles) {
if (handle.isAvailableByLookup()) {
beanDefs.add(new IOCBeanDefImplementation<>(handle, this.<Object>getType(name, handle, handle.getActualType())));
beanDefFactoryNames.add(handle.getFactoryName());
}
}
for (final JsTypeProvider<?> provider : JsArray.iterable(jsProviders)) {
logger.debug("Found JS provider for name {} from factory {}", provider.getName(), provider.getFactoryName());
if (keepJsDups || provider.getFactoryName() == null || !beanDefFactoryNames.contains(provider.getFactoryName())) {
logger.debug("Keeping JS provider for name {} from factory {}", provider.getName(), provider.getFactoryName());
beanDefs.add(new JsTypeBeanDefImplementation(provider, name));
}
else {
logger.debug("Rejecting duplicate JS provider for name {} from factory {}", provider.getName(), provider.getFactoryName());
}
}
logger.debug("Looked up {} beans: {}", beanDefs.size(), beanDefs);
return beanDefs;
}
private JsArray<JsTypeProvider<?>> getJsProviders(final String name) {
final WindowInjectionContext windowInjectionContext = WindowInjectionContextStorage.createOrGet();
// This check may be false if -generateJsInteropExports is not set
if (hasGetProvidersMethod(windowInjectionContext)) {
return windowInjectionContext.getProviders(name);
}
else {
logger.debug("Did not look up external JsTypeProviders for {}. "
+ "Hint: It looks like the WindowInjectionContext was supplied by an app "
+ "that was compiled without the flag \"-generateJsInteropExports\".", name);
return new JsArray<>(new JsTypeProvider[0]);
}
}
private static native boolean hasGetProvidersMethod(WindowInjectionContext obj)/*-{
return obj.getProviders != undefined;
}-*/;
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public <T> Collection<SyncBeanDef<T>> lookupBeans(final Class<T> type) {
return (Collection) lookupBeans(type.getName());
}
@SuppressWarnings("unchecked")
private <T> Class<T> getType(final String typeName, final FactoryHandle handle, final Class<?> defaultType) {
for (final Class<?> type : handle.getAssignableTypes()) {
if (type.getName().equals(typeName)) {
return (Class<T>) type;
}
}
return (Class<T>) defaultType;
}
@Override
public <T> Collection<SyncBeanDef<T>> lookupBeans(final Class<T> type, final Annotation... qualifiers) {
final Set<Annotation> qualifierSet = new HashSet<>(Arrays.asList(qualifiers));
final Collection<SyncBeanDef<T>> candidates = lookupBeans(type);
final Iterator<SyncBeanDef<T>> iter = candidates.iterator();
while (iter.hasNext()) {
final SyncBeanDef<T> beanDef = iter.next();
if (!beanDef.matches(qualifierSet)) {
iter.remove();
}
}
return candidates;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <T> SyncBeanDef<T> lookupBean(final Class<T> type, Annotation... qualifiers) {
if (qualifiers.length == 0) {
qualifiers = new Annotation[] { QualifierUtil.DEFAULT_ANNOTATION };
}
final Collection resolved = lookupBeans(type, qualifiers);
if (resolved.isEmpty()) {
throw BeanManagerUtil.unsatisfiedResolutionException(type, qualifiers);
} else if (resolved.size() > 1) {
throw BeanManagerUtil.ambiguousResolutionException(type, resolved, qualifiers);
} else {
return (SyncBeanDef<T>) resolved.iterator().next();
}
}
@Override
public <T> void registerBean(final SyncBeanDef<T> beanDef) {
runtimeBeanDefsByName.put(beanDef.getType().getName(), beanDef);
if (!beanDef.getType().getName().equals(beanDef.getBeanClass().getName())) {
runtimeBeanDefsByName.put(beanDef.getBeanClass().getName(), beanDef);
}
if (beanDef.getName() != null) {
runtimeBeanDefsByName.put(beanDef.getName(), beanDef);
}
}
@Override
public <T> void registerBeanTypeAlias(final SyncBeanDef<T> beanDef, final Class<?> type) {
runtimeBeanDefsByName.put(type.getName(), beanDef);
}
/**
* For testing only.
*/
public void reset() {
contextManager = null;
handlesByName.clear();
runtimeBeanDefsByName.clear();
}
// TODO Find way to properly get scope, qualifiers, and assignable types.
@SuppressWarnings("rawtypes")
private final class JsTypeBeanDefImplementation implements SyncBeanDef {
private final JsTypeProvider<?> provider;
private final String name;
private JsTypeBeanDefImplementation(final JsTypeProvider provider, final String name) {
this.provider = provider;
this.name = name;
}
@Override
public boolean isAssignableTo(final Class type) {
return Object.class.equals(type) || (type != null && type.getName().equals(name));
}
@Override
public Class getType() {
return null;
}
@Override
public Class getBeanClass() {
return null;
}
@Override
public Class getScope() {
return Dependent.class;
}
@Override
public Set getQualifiers() {
final Set<DynamicAnnotation> qualifiers = new HashSet<>();
for (final String serializedQualifier : JsArray.iterable(provider.getQualifiers())) {
qualifiers.add(DynamicAnnotation.create(serializedQualifier));
}
return qualifiers;
}
@Override
public boolean matches(final Set annotations) {
return true;
}
@Override
public String getName() {
return provider.getName();
}
@Override
public boolean isActivated() {
return true;
}
@Override
public Object getInstance() {
return provider.getInstance();
}
@Override
public Object newInstance() {
throw new UnsupportedOperationException("Cannot create new instance of JsType bean.");
}
@Override
public boolean isDynamic() {
return true;
}
@Override
public String toString() {
return "[JsTypeBeanDef: factoryName=" + provider.getFactoryName() + ", name=" + provider.getName() + "]";
}
}
private final class IOCBeanDefImplementation<T> implements SyncBeanDef<T> {
private final FactoryHandle handle;
private final Class<T> type;
private Set<Annotation> qualifiers;
private IOCBeanDefImplementation(final FactoryHandle handle, final Class<T> type) {
this.handle = handle;
this.type = type;
}
@Override
public String toString() {
return BeanManagerUtil.beanDeftoString(handle);
}
@Override
public Class<T> getType() {
return type;
}
@SuppressWarnings("unchecked")
@Override
public Class<T> getBeanClass() {
return (Class<T>) handle.getActualType();
}
@Override
public Class<? extends Annotation> getScope() {
return handle.getScope();
}
@Override
public T getInstance() {
final T instance = contextManager.getInstance(handle.getFactoryName());
if (instance instanceof Proxy) {
@SuppressWarnings("unchecked")
final Proxy<T> proxy = (Proxy<T>) instance;
// Forces bean to be loaded.
proxy.unwrap();
}
return instance;
}
@Override
public T newInstance() {
return contextManager.getNewInstance(handle.getFactoryName());
}
@Override
public Set<Annotation> getQualifiers() {
if (qualifiers == null) {
qualifiers = new HashSet<>(handle.getQualifiers());
}
return qualifiers;
}
@Override
public boolean matches(final Set<Annotation> annotations) {
return QualifierUtil.matches(annotations, handle.getQualifiers());
}
@Override
public String getName() {
return handle.getBeanName();
}
@Override
public boolean isActivated() {
final Class<? extends BeanActivator> activatorType = handle.getBeanActivatorType();
if (activatorType == null) {
return true;
} else {
final BeanActivator activator = lookupBean(activatorType).getInstance();
return activator.isActivated();
}
}
@Override
public boolean isAssignableTo(final Class<?> type) {
return handle.getAssignableTypes().contains(type);
}
}
public void addFactory(final Factory<?> factory) {
contextManager.addFactory(factory);
addFactory(factory.getHandle());
}
}