org.infinispan.cdi.common.util.BeanManagerProvider Maven / Gradle / Ivy
package org.infinispan.cdi.common.util;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.AfterDeploymentValidation;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.BeforeShutdown;
import jakarta.enterprise.inject.spi.Extension;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* This class provides access to the {@link BeanManager}
* by registering the current {@link BeanManager} in an extension and
* making it available via a singleton factory for the current application.
* This is really handy if you like to access CDI functionality
* from places where no injection is available.
* If a simple but manual bean-lookup is needed, it's easier to use the {@link BeanProvider}.
*
* As soon as an application shuts down, the reference to the {@link BeanManager} will be removed.
*
* Usage:
*
* BeanManager bm = BeanManagerProvider.getInstance().getBeanManager();
*
*
*
* Attention: This method is intended for being used in user code at runtime.
* If this method gets used during Container boot (in an Extension), non-portable
* behaviour results. During bootstrapping an Extension shall @Inject BeanManager to get
* access to the underlying BeanManager (see e.g. {@link #cleanupFinalBeanManagers} ).
* This is the only way to guarantee to get the right
* BeanManager in more complex Container scenarios.
*/
public class BeanManagerProvider implements Extension
{
private static final Logger LOG = Logger.getLogger(BeanManagerProvider.class.getName());
private static BeanManagerProvider bmpSingleton = null;
/**
* This data container is used for storing the BeanManager for each
* WebApplication. This is needed in EAR or other multi-webapp scenarios
* if the DeltaSpike classes (jars) are provided in a shared ClassLoader.
*/
private static class BeanManagerInfo
{
/**
* The BeanManager picked up via Extension loading
*/
private BeanManager loadTimeBm = null;
/**
* The final BeanManagers.
* After the container did finally boot, we first try to resolve them from JNDI,
* and only if we don't find any BM there we take the ones picked up at startup.
*/
private BeanManager finalBm = null;
/**
* Whether the CDI Application has finally booted.
* Please note that this is only a nearby value
* as there is no reliable event for this status in EE6.
*/
private boolean booted = false;
}
/**
* The BeanManagerInfo for the current ClassLoader.
* Attention: This instance must only be used through the {@link #bmpSingleton} singleton!
*/
private volatile Map bmInfos = new ConcurrentHashMap();
/**
* Returns if the {@link BeanManagerProvider} has been initialized.
* Usually it isn't needed to call this method in application code.
* It's e.g. useful for other frameworks to check if DeltaSpike and the CDI container in general have been started.
*
* @return true if the bean-manager-provider is ready to be used
*/
public static boolean isActive()
{
return bmpSingleton != null;
}
/**
* Allows to get the current provider instance which provides access to the current {@link BeanManager}
*
* @throws IllegalStateException if the {@link BeanManagerProvider} isn't ready to be used.
* That's the case if the environment isn't configured properly and therefore the {@link AfterBeanDiscovery}
* hasn't be called before this method gets called.
* @return the singleton BeanManagerProvider
*/
public static BeanManagerProvider getInstance()
{
/*X TODO Java-EE5 support needs to be discussed
if (bmpSingleton == null)
{
// workaround for some Java-EE5 environments in combination with a special
// StartupBroadcaster for bootstrapping CDI
// CodiStartupBroadcaster.broadcastStartup();
// here bmp might not be null (depends on the broadcasters)
}
*/
if (bmpSingleton == null)
{
throw new IllegalStateException("No " + BeanManagerProvider.class.getName() + " in place! " +
"Please ensure that you configured the CDI implementation of your choice properly. " +
"If your setup is correct, please clear all caches and compiled artifacts.");
}
return bmpSingleton;
}
/**
* It basically doesn't matter which of the system events we use,
* but basically we use the {@link AfterBeanDiscovery} event since it allows to use the
* {@link BeanManagerProvider} for all events which occur after the {@link AfterBeanDiscovery} event.
*
* @param afterBeanDiscovery event which we don't actually use ;)
* @param beanManager the BeanManager we store and make available.
*/
public void setBeanManager(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager)
{
setBeanManagerProvider(this);
BeanManagerInfo bmi = getBeanManagerInfo(getClassLoader(null));
bmi.loadTimeBm = beanManager;
}
/**
* The active {@link BeanManager} for the current application (/{@link ClassLoader}). This method will throw an
* {@link IllegalStateException} if the BeanManager cannot be found.
*
* @return the current bean-manager, never null
* @throws IllegalStateException if the BeanManager cannot be found
*/
public BeanManager getBeanManager()
{
BeanManagerInfo bmi = getBeanManagerInfo(getClassLoader(null));
// warn the user if he tries to use the BeanManager before container startup
if (!bmi.booted)
{
LOG.warning("When using the BeanManager to retrieve Beans before the Container is started," +
" non-portable behaviour results!");
}
BeanManager result = bmi.finalBm;
if (result == null)
{
synchronized (this)
{
result = bmi.finalBm;
if (result == null)
{
// first we look for a BeanManager from JNDI
result = resolveBeanManagerViaJndi();
if (result == null)
{
// if none found, we take the one we got from the Extension loading
result = bmi.loadTimeBm;
}
if (result == null)
{
throw new IllegalStateException("Unable to find BeanManager. " +
"Please ensure that you configured the CDI implementation of your choice properly.");
}
// store the resolved BeanManager in the result cache until #cleanupFinalBeanManagers gets called
// -> afterwards the next call of #getBeanManager will trigger the final lookup
bmi.finalBm = result;
}
}
}
return result;
} /**
* Detect the right ClassLoader.
* The lookup order is determined by:
*
* - ContextClassLoader of the current Thread
* - ClassLoader of the given Object 'o'
* - ClassLoader of this very ClassUtils class
*
*
* @param o if not null
it may get used to detect the classloader.
* @return The {@link ClassLoader} which should get used to create new instances
*/
public static ClassLoader getClassLoader(Object o)
{
if (System.getSecurityManager() != null)
{
return AccessController.doPrivileged(new GetClassLoaderAction(o));
}
else
{
return getClassLoaderInternal(o);
}
}
static class GetClassLoaderAction implements PrivilegedAction
{
private Object object;
GetClassLoaderAction(Object object)
{
this.object = object;
}
@Override
public ClassLoader run()
{
try
{
return getClassLoaderInternal(object);
}
catch (Exception e)
{
return null;
}
}
}
private static ClassLoader getClassLoaderInternal(Object o)
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null && o != null)
{
loader = o.getClass().getClassLoader();
}
if (loader == null)
{
loader = BeanManagerProvider.class.getClassLoader();
}
return loader;
}
/**
* By cleaning the final BeanManager map after the Deployment got Validated,
* we prevent premature loading of information from JNDI in cases where the
* container might not be fully setup yet.
*
* This might happen if someone uses the BeanManagerProvider during Extension
* startup.
*/
public void cleanupFinalBeanManagers(@Observes AfterDeploymentValidation adv)
{
for (BeanManagerInfo bmi : bmpSingleton.bmInfos.values())
{
bmi.finalBm = null;
bmi.booted = true;
/*possible issue with >weld< based servers:
if #getBeanManager gets called in a custom AfterDeploymentValidation observer >after< this observer,
the wrong bean-manager might get stored (not deterministic due to the unspecified order of observers).
finally a bean-manager for a single bda will be stored and returned (which isn't the bm exposed via jndi).*/
}
}
/**
* Cleanup on container shutdown
*
* @param beforeShutdown cdi shutdown event
*/
public void cleanupStoredBeanManagerOnShutdown(@Observes BeforeShutdown beforeShutdown)
{
if (bmpSingleton == null)
{
// this happens if there has been a failure at startup
return;
}
ClassLoader classLoader = getClassLoader(null);
bmpSingleton.bmInfos.remove(classLoader);
//X TODO this might not be enough as there might be
//X ClassLoaders used during Weld startup which are not the TCCL...
}
/**
* Get the BeanManager from the JNDI registry.
*
* @return current {@link BeanManager} which is provided via JNDI
*/
private BeanManager resolveBeanManagerViaJndi()
{
try
{
// this location is specified in JSR-299 and must be
// supported in all certified EE environments
return (BeanManager) new InitialContext().lookup("java:comp/BeanManager");
}
catch (NamingException e)
{
//workaround didn't work -> return null
return null;
}
}
/**
* Get or create the BeanManagerInfo for the given ClassLoader
*/
private BeanManagerInfo getBeanManagerInfo(ClassLoader cl)
{
BeanManagerInfo bmi = bmpSingleton.bmInfos.get(cl);
if (bmi == null)
{
synchronized (this)
{
bmi = bmpSingleton.bmInfos.get(cl);
if (bmi == null)
{
bmi = new BeanManagerInfo();
bmpSingleton.bmInfos.put(cl, bmi);
}
}
}
return bmi;
}
/**
* This function exists to prevent findbugs to complain about
* setting a static member from a non-static function.
*
* @param beanManagerProvider the bean-manager-provider which should be used if there isn't an existing provider
* @return the first BeanManagerProvider
*/
private static BeanManagerProvider setBeanManagerProvider(BeanManagerProvider beanManagerProvider)
{
if (bmpSingleton == null)
{
bmpSingleton = beanManagerProvider;
}
return bmpSingleton;
}
}