/* * Copyright (C) 2012 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 org.jboss.errai.common.client.api.Assert; import org.jboss.errai.common.client.util.CreationalCallback; import org.jboss.errai.ioc.client.container.IOC; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * An <tt>AsyncBeanQuery</tt> is used for querying more than one bean at a time, and then orchestrating * a unit of work to happen only after all of the beans in the query have been successfully loaded. * <p/> * For instance, you may need to load two or more beans dynamically from the bean manager in order to * accomplish some task. Instead of chaining a series of callbacks, which can become difficult to understand * and maintain, you can instead construct a query for the set of beans you need. * <p/> * Example: * <pre><code> * AsyncBeanQuery beanQuery = new AsyncBeanQuery(); * final AsyncBeanFuture<BeanA> beanAFuture = beanQuery.load(BeanA.class); * final AsyncBeanFuture<BeanB> beanBFuture = beanQuery.load(BeanB.class); * <p/> * beanQuery.query(new Runnable() { * public void run() { * BeanA beanA = beanAFuture.get(); * BeanB beanB = beanBFuture.get(); * // do some work. * } * }); * </code></pre> * <p/> * In the above example, the <tt>Runnable</tt> passed to {@link #query(Runnable)} will be executed only after * all the beans requested with the previous {@link #load(AsyncBeanDef)} calls have been satisfied. * * @author Mike Brock * @since 3.0 */ public class AsyncBeanQuery { private boolean cancelled = false; private Runnable finishCallback; private final List<LoadStrategy> loadStrategies = new ArrayList<LoadStrategy>(); private final Set<LoadStrategy> loaded = new HashSet<LoadStrategy>(); /** * Requests that the specified bean of the specified {@param type} is loaded. * * @param type * the type of the bean to be loaded. See: {@link AsyncBeanManagerImpl#lookupBean(Class, java.lang.annotation.Annotation...)} * @param <T> * the type of bean to be loaded. * * @return an {@link AsyncBeanFuture} which will house the instance of the bean once it is loaded. */ public <T> AsyncBeanFuture<T> load(final Class<T> type) { return load(IOC.getAsyncBeanManager().lookupBean(type)); } /** * Requests that the specified bean of the specified {@param type} and {@param qualifiers} is loaded. * * @param type * the type of the bean to be loaded. See: {@link AsyncBeanManagerImpl#lookupBean(Class, java.lang.annotation.Annotation...)} * @param qualifiers * the qualifiers for the bean to be loaded. * @param <T> * the type of bean to be loaded. * * @return an {@link AsyncBeanFuture} which will house the instance of the bean once it is loaded. */ public <T> AsyncBeanFuture<T> load(final Class<T> type, final Annotation... qualifiers) { return load(IOC.getAsyncBeanManager().lookupBean(type, qualifiers)); } /** * Requests that the bean described by the specified {@link AsyncBeanDef} is loaded. * * @param beanDef * the {@link AsyncBeanDef} describing the bean to be loaded. * @param <T> * the type of bean to be loaded. * * @return an {@link AsyncBeanFuture} which will house the instance of the bean once it is loaded. */ public <T> AsyncBeanFuture<T> load(final AsyncBeanDef<T> beanDef) { return load(beanDef, false); } /** * Requests that a new instance specified bean of the specified {@param type} is loaded. * * @param type * the type of the bean to be loaded. See: {@link AsyncBeanManagerImpl#lookupBean(Class, java.lang.annotation.Annotation...)} * @param <T> * the type of bean to be loaded. * * @return an {@link AsyncBeanFuture} which will house the instance of the bean once it is loaded. */ public <T> AsyncBeanFuture<T> loadNew(final Class<T> type) { return loadNew(IOC.getAsyncBeanManager().lookupBean(type)); } /** * Requests that a new instance of specified bean of the specified {@param type} and {@param qualifiers} is loaded. * * @param type * the type of the bean to be loaded. See: {@link AsyncBeanManagerImpl#lookupBean(Class, java.lang.annotation.Annotation...)} * @param qualifiers * the qualifiers for the bean to be loaded. * @param <T> * the type of bean to be loaded. * * @return an {@link AsyncBeanFuture} which will house the instance of the bean once it is loaded. */ public <T> AsyncBeanFuture<T> loadNew(final Class<T> type, final Annotation... qualifiers) { return loadNew(IOC.getAsyncBeanManager().lookupBean(type, qualifiers)); } /** * Requests that a new instance of the bean described by the specified {@link AsyncBeanDef} is loaded. * * @param beanDef * the {@link AsyncBeanDef} describing the bean to be loaded. * @param <T> * the type of bean to be loaded. * * @return an {@link AsyncBeanFuture} which will house the instance of the bean once it is loaded. */ public <T> AsyncBeanFuture<T> loadNew(final AsyncBeanDef<T> beanDef) { return load(beanDef, true); } private <T> AsyncBeanFuture<T> load(final AsyncBeanDef<T> beanDef, final boolean loadNew) { final LoadStrategy<T> loadStrategy = new LoadStrategy<T>(beanDef, loadNew); loadStrategies.add(loadStrategy); return loadStrategy.getFuture(); } /** * Initiates the constructed query based on {@link #load(AsyncBeanDef)} and {@link #loadNew(AsyncBeanDef)} calls * made prior to calling this method. The specified <tt>Runnable</tt> is invoked only after all the requested * beans have been successfully loaded. * * @param finishCallback * the <tt>Runnable</tt> to be invoked once all of the request beans have been loaded. */ public void query(final Runnable finishCallback) { Assert.notNull(finishCallback); this.finishCallback = finishCallback; for (final LoadStrategy strategy : loadStrategies) { strategy.load(); } } /** * Cancels the query if it has not yet returned. Already loaded beans are destroyed. */ public void cancelQuery() { cancelled = true; for (final LoadStrategy strategy : loaded) { final AsyncBeanFuture future = strategy.getFuture(); IOC.getAsyncBeanManager().destroyBean(future.get()); } loaded.clear(); loadStrategies.clear(); } private void vote(LoadStrategy<?> strategy) { if (cancelled) { IOC.getAsyncBeanManager().destroyBean(strategy.getFuture().get()); return; } loaded.add(strategy); if (loaded.containsAll(loadStrategies)) { finishCallback.run(); } } private class LoadStrategy<T> { private final AsyncBeanDef<T> beanDef; private final AsyncBeanFuture<T> future; private final boolean loadNew; private LoadStrategy(final AsyncBeanDef<T> beanDef, final boolean loadNew) { this.beanDef = beanDef; this.loadNew = loadNew; this.future = new AsyncBeanFuture<T>(new CreationalCallback<T>() { @Override public void callback(T beanInstance) { vote(LoadStrategy.this); } }); } AsyncBeanDef<T> getBeanDef() { return beanDef; } AsyncBeanFuture<T> getFuture() { return future; } boolean isLoadNew() { return loadNew; } void load() { if (loadNew) { beanDef.newInstance(future.getCreationalCallback()); } else { beanDef.getInstance(future.getCreationalCallback()); } } } }