/* * Copyright (C) 2015 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.async; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; import javax.enterprise.inject.Alternative; import javax.inject.Provider; import org.jboss.errai.common.client.util.CreationalCallback; import org.jboss.errai.ioc.client.QualifierUtil; import org.jboss.errai.ioc.client.container.BeanManagerSetup; import org.jboss.errai.ioc.client.container.BeanManagerUtil; import org.jboss.errai.ioc.client.container.ContextManager; import org.jboss.errai.ioc.client.container.DestructionCallback; import org.jboss.errai.ioc.client.container.Factory; import org.jboss.errai.ioc.client.container.FactoryHandle; import org.jboss.errai.ioc.client.container.RefHolder; import org.jboss.errai.ioc.client.container.SyncBeanDef; import org.jboss.errai.ioc.client.container.SyncBeanManager; import org.jboss.errai.ioc.client.container.SyncBeanManagerImpl; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; /** * @author Max Barkley <mbarkley@redhat.com> */ @Alternative public class AsyncBeanManagerImpl implements AsyncBeanManager, BeanManagerSetup, AsyncBeanManagerSetup { private final SyncBeanManagerImpl innerBeanManager = new SyncBeanManagerImpl(); private final Multimap<String, String> typeNamesByName = HashMultimap.create(); private final Multimap<String, UnloadedFactory<?>> unloadedByTypeName = HashMultimap.create(); private final Map<String, UnloadedFactory<?>> unloadedByFactoryName = new HashMap<>(); @Override public void destroyBean(final Object ref) { innerBeanManager.destroyBean(ref); } @Override public boolean isManaged(final Object ref) { return innerBeanManager.isManaged(ref); } @Override public Object getActualBeanReference(final Object ref) { return innerBeanManager.getActualBeanReference(ref); } @Override public boolean isProxyReference(final Object ref) { return innerBeanManager.isProxyReference(ref); } @Override public boolean addDestructionCallback(final Object beanInstance, final DestructionCallback<?> destructionCallback) { return innerBeanManager.addDestructionCallback(beanInstance, destructionCallback); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Collection<AsyncBeanDef> lookupBeans(final String name) { final Collection syncBeans = innerBeanManager.lookupBeans(name); final Collection beans = wrapSyncBeans(syncBeans); addUnloadedBeans(beans, name); return beans; } private <T> Collection<AsyncBeanDef<T>> wrapSyncBeans(final Collection<SyncBeanDef<T>> syncBeans) { final Collection<AsyncBeanDef<T>> asyncBeans = new ArrayList<>(syncBeans.size()); for (final SyncBeanDef<T> syncBean : syncBeans) { asyncBeans.add(new SyncToAsyncBeanDef<>(syncBean)); } return asyncBeans; } @Override public <T> Collection<AsyncBeanDef<T>> lookupBeans(final Class<T> type) { return lookupBeans(type, QualifierUtil.DEFAULT_ANNOTATION); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public <T> Collection<AsyncBeanDef<T>> lookupBeans(final Class<T> type, Annotation... qualifiers) { if (qualifiers.length == 0) { qualifiers = new Annotation[] { QualifierUtil.DEFAULT_ANNOTATION }; } final Collection beans = wrapSyncBeans(innerBeanManager.lookupBeans(type, qualifiers)); addUnloadedBeans(beans, type, type.getName(), qualifiers); return beans; } @SuppressWarnings({ "unchecked", "rawtypes" }) private <T> void addUnloadedBeans(final Collection<AsyncBeanDef> beans, final Class<T> type, final String typeName, final Annotation... qualifiers) { final Collection<UnloadedFactory<?>> unloadedCandidates = unloadedByTypeName.get(typeName); final Collection<Annotation> allOf = Arrays.asList(qualifiers); for (final UnloadedFactory unloaded : unloadedCandidates) { if (QualifierUtil.matches(allOf, unloaded.getHandle().getQualifiers())) { final Class<T> beanType = (Class<T>) (type != null ? type : unloaded.getHandle().getActualType()); beans.add(unloaded.createBeanDef(beanType)); } } } @SuppressWarnings("rawtypes") private void addUnloadedBeans(final Collection<AsyncBeanDef> beans, final String name) { for (final String typeName : typeNamesByName.get(name)) { addUnloadedBeans(beans, null, typeName, QualifierUtil.DEFAULT_ANNOTATION); } } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public <T> AsyncBeanDef<T> lookupBean(final Class<T> type, final Annotation... qualifiers) { final Collection beans = lookupBeans(type, qualifiers); if (beans.size() > 1) { throw BeanManagerUtil.ambiguousResolutionException(type, beans, qualifiers); } else if (beans.isEmpty()) { throw BeanManagerUtil.unsatisfiedResolutionException(type, qualifiers); } else { return (AsyncBeanDef<T>) beans.iterator().next(); } } @Override public void setContextManager(final ContextManager contextManager) { innerBeanManager.setContextManager(contextManager); } public void reset() { typeNamesByName.clear(); unloadedByTypeName.clear(); innerBeanManager.reset(); } public SyncBeanManager getInnerBeanManager() { return innerBeanManager; } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public void registerAsyncBean(final FactoryHandle handle, final FactoryLoader<?> future) { final String beanName; if (handle.getBeanName() != null) { beanName = handle.getBeanName(); } else { beanName = handle.getActualType().getName(); } typeNamesByName.put(beanName, handle.getActualType().getName()); final UnloadedFactory unloaded = new UnloadedFactory(handle, future); for (final Class<?> assignable : handle.getAssignableTypes()) { unloadedByTypeName.put(assignable.getName(), unloaded); } unloadedByFactoryName.put(handle.getFactoryName(), unloaded); } private void unregisterAsyncBean(final FactoryHandle handle) { final String name = (handle.getBeanName() != null ? handle.getBeanName() : handle.getActualType().getName()); typeNamesByName.remove(name, handle.getActualType().getName()); unloadedByFactoryName.remove(handle.getFactoryName()); for (final Class<?> assignable : handle.getAssignableTypes()) { final Iterator<UnloadedFactory<?>> unloadedIter = unloadedByTypeName.get(assignable.getName()).iterator(); while (unloadedIter.hasNext()) { final UnloadedFactory<?> unloaded = unloadedIter.next(); if (unloaded.getHandle().getFactoryName().equals(handle.getFactoryName())) { unloadedIter.remove(); break; } } } } @Override public void registerAsyncDependency(final String dependentFactoryName, final String dependencyFactoryName) { final UnloadedFactory<?> unloaded = getUnloadedFactory(dependentFactoryName); unloaded.addAsyncDependency(dependencyFactoryName); } private UnloadedFactory<?> getUnloadedFactory(final String factoryName) { final UnloadedFactory<?> unloadedFactory = unloadedByFactoryName.get(factoryName); if (unloadedFactory == null) { throw new RuntimeException("No unloaded factory found for " + factoryName); } else { return unloadedFactory; } } private class UnloadedFactory<T> { private final FactoryHandle handle; private final FactoryLoader<T> loader; private final Set<String> asyncDependencies = new HashSet<>(); private boolean loaded = false; private boolean loading = false; private final Queue<Runnable> onLoad = new LinkedList<>(); public UnloadedFactory(final FactoryHandle handle, final FactoryLoader<T> loader) { this.handle = handle; this.loader = loader; } private void loadSelf(final Runnable whenLoaded) { if (loaded) { whenLoaded.run(); return; } else if (loading) { onLoad.add(whenLoaded); return; } else { loading = true; loader.call(new FactoryLoaderCallback<T>() { @Override public void callback(final Factory<T> factory) { innerBeanManager.addFactory(factory); unregisterAsyncBean(handle); whenLoaded.run(); } }); } } public void load(final Runnable onFinish) { if (loaded) { onFinish.run(); return; } else if (loading) { onLoad.add(onFinish); return; } loading = true; onLoad.add(onFinish); final RefHolder<Integer> numLoaded = new RefHolder<>(); numLoaded.set(0); final Collection<UnloadedFactory<?>> unloadedDeps = getUnloadedAsyncDependencies(); for (final UnloadedFactory<?> unloadedDep : unloadedDeps) { unloadedDep.loadSelf(new Runnable() { @Override public void run() { numLoaded.set(numLoaded.get()+1); if (numLoaded.get().equals(unloadedDeps.size()+1)) { finishLoading(unloadedDeps); } } }); } loader.call(new FactoryLoaderCallback<T>() { @Override public void callback(final Factory<T> factory) { numLoaded.set(numLoaded.get()+1); innerBeanManager.addFactory(factory); unregisterAsyncBean(handle); if (numLoaded.get().equals(unloadedDeps.size()+1)) { finishLoading(unloadedDeps); } } }); } protected void finishLoading(final Collection<UnloadedFactory<?>> unloadedDeps) { finishDependencies(unloadedDeps); finishSelf(); } private void finishDependencies(final Collection<UnloadedFactory<?>> unloadedDeps) { for (final UnloadedFactory<?> unloaded : unloadedDeps) { unloaded.finishSelf(); } } private void finishSelf() { loading = false; loaded = true; while (!onLoad.isEmpty()) { onLoad.poll().run(); } } private Collection<UnloadedFactory<?>> getUnloadedAsyncDependencies() { final Deque<UnloadedFactory<?>> unloadedDeps = new LinkedList<>(); final Queue<String> bfsQueue = new LinkedList<>(asyncDependencies); final Set<String> visited = new HashSet<>(); visited.add(handle.getFactoryName()); while (bfsQueue.size() > 0) { final String factoryName = bfsQueue.poll(); if (visited.contains(factoryName)) { continue; } final UnloadedFactory<?> unloadedDep = unloadedByFactoryName.get(factoryName); if (unloadedDep != null && !unloadedDep.loaded) { unloadedDeps.addFirst(unloadedDep); visited.add(factoryName); for (final String dependentFactoryName : unloadedDep.asyncDependencies) { bfsQueue.add(dependentFactoryName); } } } return unloadedDeps; } public FactoryHandle getHandle() { return handle; } public void addAsyncDependency(final String factoryName) { asyncDependencies.add(factoryName); } public FactoryLoaderBeanDef createBeanDef(final Class<T> type) { return new FactoryLoaderBeanDef(type); } private class FactoryLoaderBeanDef implements AsyncBeanDef<T> { private final Class<T> type; private Set<Annotation> qualifiers; public FactoryLoaderBeanDef(final Class<T> type) { this.type = type; } @Override public Class<T> getType() { return type; } @Override public Class<?> getBeanClass() { return handle.getActualType(); } @Override public Class<? extends Annotation> getScope() { return handle.getScope(); } @Override public void getInstance(final CreationalCallback<T> callback) { getInstanceHelper(callback, new Provider<T>() { @Override public T get() { return performSyncLookup().getInstance(); } }); } @Override public void newInstance(final CreationalCallback<T> callback) { getInstanceHelper(callback, new Provider<T>() { @Override public T get() { return performSyncLookup().newInstance(); } }); } private void getInstanceHelper(final CreationalCallback<T> callback, final Provider<T> instanceProvider) { if (!loaded) { load(new Runnable() { @Override public void run() { callback.callback(instanceProvider.get()); } }); } else { callback.callback(instanceProvider.get()); } } private SyncBeanDef<T> performSyncLookup() { return innerBeanManager.lookupBean(type, handle.getQualifiers().toArray(new Annotation[0])); } @Override public Set<Annotation> getQualifiers() { if (qualifiers == null) { qualifiers = Collections.unmodifiableSet(new HashSet<>(handle.getQualifiers())); } return qualifiers; } @Override public boolean matches(final Set<Annotation> annotations) { return QualifierUtil.matches(handle.getQualifiers(), annotations); } @Override public String getName() { return handle.getBeanName(); } @Override public boolean isActivated() { return true; } @Override public String toString() { return BeanManagerUtil.beanDeftoString(handle); } @Override public boolean isAssignableTo(final Class<?> type) { return handle.getAssignableTypes().contains(type); } } } }