/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.resin;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Enumeration;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import com.caucho.config.ConfigException;
import com.caucho.config.inject.InjectManager;
import com.caucho.ejb.manager.EjbEnvironmentListener;
import com.caucho.ejb.manager.EjbManager;
import com.caucho.env.jpa.ListenerPersistenceEnvironment;
import com.caucho.inject.ThreadContext;
import com.caucho.java.WorkDir;
import com.caucho.loader.CompilingLoader;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.ResourceLoader;
import com.caucho.server.webbeans.ResinCdiProducer;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.Vfs;
/**
* Embeddable Resin context for testing of bean container components (CDI
* managed beans, EJBs, JPA) in an environment that mirrors a production runtime
* but without the overhead of the Resin server. The ResinBeanContainer can be
* embedded into any Java SE environment, including a JUnit test. Note, the bean
* container does not support Servlet-based APIs. In order to test the web tier,
* <code>ResinEmbed</code> should be used.
*
* <code><pre>
* static void main(String []args)
* {
* ResinBeanContainer beans = new ResinBeanContainer();
*
* beans.addModule("test.jar");
* beans.start();
*
* RequestContext req = beans.beginRequest();
* try {
* MyMain main = beans.getInstance(MyMain.class);
*
* main.main(args);
* } finally {
* req.close();
* }
*
* beans.close();
* }
* </pre></code>
*
* <h2>Configuration File</h2>
*
* The optional configuration file for the ResinContext allows the same
* environment and bean configuration as the resin-web.xml, but without the
* servlet-specific configuration.
*
* <pre>
* <code>
* <beans xmlns="http://caucho.com/ns/resin"
* xmlns:resin="urn:java:com.caucho.resin">
*
* <resin:import path="${__DIR__}/my-include.xml"/>
*
* <database name="my-database">
* <driver ...>
* ...
* </driver>
* </database>
*
* <mypkg:MyBean xmlns:mypkg="urn:java:com.mycom.mypkg">
* <my-property>my-data</my-property>
* </mypkg:MyBean>
* </beans>
* </code>
* </pre>
*/
// TODO Add JNDI look-up and well as direct access to JNDI/CDI beans manager.
public class ResinBeanContainer {
private static final Logger log
= Logger.getLogger(ResinBeanContainer.class.getName());
private static final L10N L = new L10N(ResinBeanContainer.class);
private EnvironmentClassLoader _classLoader;
private InjectManager _cdiManager;
private ThreadLocal<BeanContainerRequest> _localContext = new ThreadLocal<BeanContainerRequest>();
// Path to the current module (typically the current directory)
private Path _modulePath;
/**
* Creates a new Resin context.
*/
public ResinBeanContainer()
{
_classLoader = EnvironmentClassLoader.create("resin-context");
_cdiManager = InjectManager.create(_classLoader);
// ioc/0b07
_cdiManager.replaceContext(new RequestScope());
_cdiManager.replaceContext(ThreadContext.getContext());
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
// ioc/0p62
EjbManager.create(_classLoader);
// XXX: currently this would cause a scanning of the class-path even
// if there's no ejb-jar.xml
// EjbManager.setScanAll();
Environment.init();
Environment.addChildLoaderListener(new ListenerPersistenceEnvironment());
Environment.addChildLoaderListener(new EjbEnvironmentListener());
Environment.addCloseListener(this);
Class<?> resinCdiProducer = getResinCdiProducerClass();
if (resinCdiProducer != null)
_cdiManager.addManagedBean(_cdiManager.createManagedBean(resinCdiProducer));
Class<?> resinValidatorClass
= ResinCdiProducer.createResinValidatorProducer();
if (_cdiManager != null && resinValidatorClass != null)
_cdiManager.addManagedBean(_cdiManager.createManagedBean(resinValidatorClass));
_classLoader.addScanRoot();
} finally {
thread.setContextClassLoader(oldLoader);
}
}
public void setId(String id)
{
_classLoader.setId(id);
}
public InjectManager getCdiManager()
{
return _cdiManager;
}
public void setModule(String modulePath)
{
_modulePath = Vfs.lookup(modulePath);
}
/**
* Adds a new module (jar or exploded classes directory)
*/
public void addClassPath(String classPath)
{
Path path = Vfs.lookup(classPath);
if (classPath.endsWith(".jar")) {
_classLoader.addJar(path);
} else {
CompilingLoader loader = new CompilingLoader(_classLoader);
loader.setPath(path);
loader.init();
}
}
/**
* Adds a package as module root.
*
* @param packageName
* the name of the package to be treated as a virtual module root.
*/
public void addPackageModule(String modulePath, String packageName)
{
Path root = Vfs.lookup(modulePath);
try {
URL url = new URL(root.getURL());
_classLoader.addScanPackage(url, packageName);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
/**
* Adds a package in the classpath as module root.
*
* @param packageName
* the name of the package to be treated as a virtual module root.
*/
public void addPackageModule(String packageName)
{
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> e = loader.getResources(packageName.replace('.', '/'));
URL bestUrl = null;
while (e.hasMoreElements()) {
URL url = e.nextElement();
if (bestUrl == null) {
bestUrl = url;
continue;
}
URL urlA = bestUrl;
Path pathA = Vfs.lookup(urlA);
Path pathB = Vfs.lookup(url);
for (String name : pathA.list()) {
if (name.endsWith(".class")) {
bestUrl = urlA;
break;
}
}
for (String name : pathB.list()) {
if (name.endsWith(".class")) {
bestUrl = url;
break;
}
}
}
if (bestUrl == null)
throw new NullPointerException(packageName);
Path path = Vfs.lookup(bestUrl);
String moduleName = path.getNativePath();
if (moduleName.endsWith(packageName.replace('.', '/'))) {
int prefixLength = moduleName.length() - packageName.length();
moduleName = moduleName.substring(0, prefixLength);
}
addResourceRoot(path);
addPackageModule(moduleName, packageName);
} catch (Exception e) {
throw ConfigException.create(e);
}
}
/**
* Adds a Resin beans configuration file, allowing creation of databases or
* bean configuration.
*
* @param pathName
* URL/path to the configuration file
*/
public void addBeansXml(String pathName)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(getClassLoader());
Path path = Vfs.lookup(pathName);
// support/041a
/*
if (_modulePath != null)
_cdiManager.addBeansXmlOverride(_modulePath, path);
else
_cdiManager.addXmlPath(path);
*/
_cdiManager.addXmlPath(path);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
public void addResourceRoot(Path path)
{
ResourceLoader loader = new ResourceLoader(_classLoader, path);
loader.init();
}
/**
* Sets the work directory for Resin to use when generating temporary files.
*/
public void setWorkDirectory(String path)
{
WorkDir.setLocalWorkDir(Vfs.lookup(path), _classLoader);
}
/**
* Initializes the context.
*/
public void start()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
// env/0e81
_cdiManager.update();
_classLoader.start();
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns a new instance of the given type with optional bindings. If the
* type is a managed bean, it will be injected before returning.
*
* @param className
* the className of the bean to instantiate
* @param qualifier
* optional @Qualifier annotations to select the bean
*/
public Object getInstance(String className, Annotation... qualifiers)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Class<?> cl = Class.forName(className, false, _classLoader);
return getInstance(cl, qualifiers);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns a new instance of the given type with optional qualifiers.
*/
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> type, Annotation... qualifiers)
{
if (type == null)
throw new NullPointerException();
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Set<Bean<?>> beans = _cdiManager.getBeans(type, qualifiers);
if (beans.size() > 0) {
Bean<?> bean = _cdiManager.resolve(beans);
return (T) _cdiManager.getReference(bean);
}
return type.newInstance();
} catch (InstantiationException e) {
// XXX: proper exception
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// XXX: proper exception
throw new RuntimeException(e);
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Returns an instance of the bean with the given name. If the type is a
* managed bean, it will be injected before returning.
*
* @param name
* the @Named of the bean to instantiate
*/
public Object getBeanByName(String name)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(_classLoader);
Set<Bean<?>> beans = _cdiManager.getBeans(name);
if (beans.size() > 0) {
Bean<?> bean = _cdiManager.resolve(beans);
return _cdiManager.getReference(bean);
}
return null;
} finally {
thread.setContextClassLoader(oldLoader);
}
}
/**
* Executes code in the Resin bean classloader and creates a request scope.
*
* <code><pre>
* resinBean.request (new Runnable() {
* doMyCode();
* });
* </pre></code>
*
* @return the RequestContext which must be passed to
* <code>completeContext</code>
*/
public void request(Runnable runnable)
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
BeanContainerRequest oldContext = _localContext.get();
BeanContainerRequest context = new BeanContainerRequest(this, oldLoader,
oldContext);
thread.setContextClassLoader(_classLoader);
_localContext.set(context);
try {
runnable.run();
} finally {
context.close();
}
}
/**
* Enters the Resin context and begins a new request on the thread. The the
* returned context must be passed to the completeRequest. To ensure the
* request is properly closed, use the following pattern:
*
* <code><pre>
* ResinContext resinContext = ...;
*
* RequestContext cxt = resinContext.beginRequest();
*
* try {
* // ... actions inside the Resin request context
* } finally {
* resinContext.completeRequest(cxt);
* }
* </pre></code>
*
* @return the RequestContext which must be passed to
* <code>completeContext</code>
*/
public BeanContainerRequest beginRequest()
{
Thread thread = Thread.currentThread();
ClassLoader oldLoader = thread.getContextClassLoader();
BeanContainerRequest oldContext = _localContext.get();
BeanContainerRequest context = new BeanContainerRequest(this, oldLoader,
oldContext);
thread.setContextClassLoader(_classLoader);
_localContext.set(context);
return context;
}
/**
* Completes the thread's request and exits the Resin context.
*/
public void completeRequest(BeanContainerRequest context)
{
Thread thread = Thread.currentThread();
thread.setContextClassLoader(context.getOldClassLoader());
_localContext.set(context.getOldContext());
}
/**
* Shuts the context down.
*/
public void close()
{
EnvironmentClassLoader loader = _classLoader;
_classLoader = null;
if (loader != null) {
loader.destroy();
}
}
public ClassLoader getClassLoader()
{
return _classLoader;
}
private Class<?> getResinCdiProducerClass()
{
try {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return Class.forName("com.caucho.server.webbeans.ResinCdiProducer",
false,
loader);
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
return null;
}
}
@Override
public String toString()
{
return getClass().getName() + "[]";
}
private class RequestScope implements Context {
@Override
public <T> T get(Contextual<T> bean)
{
BeanContainerRequest cxt = _localContext.get();
if (cxt == null)
throw new IllegalStateException(L.l("No RequestScope is active"));
return cxt.get(bean);
}
@Override
public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext)
{
BeanContainerRequest cxt = _localContext.get();
if (cxt == null)
throw new IllegalStateException(L.l("No RequestScope is active"));
return cxt.get(bean, creationalContext, _cdiManager);
}
@Override
public Class<? extends Annotation> getScope()
{
return RequestScoped.class;
}
@Override
public boolean isActive()
{
return _localContext.get() != null;
}
}
}